chore: apply eslint

This commit is contained in:
Anthony Fu
2024-02-21 17:47:45 +01:00
parent 837ffab168
commit 7dfdf887c4
14 changed files with 2151 additions and 515 deletions

View File

@@ -1,6 +0,0 @@
node_modules
demo
src
tsconfig.json
tslint.json
.gitignore

43
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,43 @@
{
// Enable the ESlint flat config support
"eslint.experimental.useFlatConfig": true,
// Disable the default formatter, use eslint instead
"prettier.enable": false,
"editor.formatOnSave": false,
// Auto fix
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.organizeImports": "never"
},
// Silent the stylistic rules in you IDE, but still auto fix them
"eslint.rules.customizations": [
{ "rule": "style/*", "severity": "off" },
{ "rule": "format/*", "severity": "off" },
{ "rule": "*-indent", "severity": "off" },
{ "rule": "*-spacing", "severity": "off" },
{ "rule": "*-spaces", "severity": "off" },
{ "rule": "*-order", "severity": "off" },
{ "rule": "*-dangle", "severity": "off" },
{ "rule": "*-newline", "severity": "off" },
{ "rule": "*quotes", "severity": "off" },
{ "rule": "*semi", "severity": "off" }
],
// Enable eslint for all supported languages
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact",
"vue",
"html",
"markdown",
"json",
"jsonc",
"yaml",
"toml"
]
}

View File

@@ -6,6 +6,6 @@ export default defineBuildConfig({
],
declaration: true,
rollup: {
emitCJS: true
}
emitCJS: true,
},
})

5
eslint.config.js Normal file
View File

@@ -0,0 +1,5 @@
import antfu from '@antfu/eslint-config'
export default antfu({
})

View File

@@ -1,41 +1,42 @@
{
"name": "@slidev/rough-notation",
"version": "0.0.1",
"type": "module",
"version": "0.0.1",
"description": "Create and animate hand-drawn annotations on a web page",
"main": "dist/index.mjs",
"module": "dist/index.cjs",
"types": "dist/index.d.mts",
"scripts": {
"build": "unbuild",
"prepack": "npm run build",
"release": "bumpp && pnpm publish",
"lint": "tslint -p tsconfig.json"
},
"author": "Preet Shihn",
"license": "MIT",
"homepage": "https://github.com/slidevjs/rough-notation#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/slidev/rough-notation.git"
"url": "git+https://github.com/slidevjs/rough-notation.git"
},
"bugs": {
"url": "https://github.com/slidevjs/rough-notation/issues"
},
"keywords": [
"annotate",
"rough",
"sketchy"
],
"main": "dist/index.mjs",
"module": "dist/index.cjs",
"types": "dist/index.d.mts",
"files": [
"dist"
],
"author": "Preet Shihn",
"license": "MIT",
"bugs": {
"url": "https://github.com/pshihn/rough-notation/issues"
"scripts": {
"build": "unbuild",
"prepack": "npm run build",
"release": "bumpp && pnpm publish",
"lint": "eslint ."
},
"homepage": "https://github.com/pshihn/rough-notation#readme",
"dependencies": {
"roughjs": "^4.6.6"
},
"devDependencies": {
"@antfu/eslint-config": "^2.6.4",
"bumpp": "^9.3.0",
"tslint": "^6.1.3",
"eslint": "^8.56.0",
"typescript": "^5.3.3",
"unbuild": "^2.0.0"
}

1809
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
import resolve from 'rollup-plugin-node-resolve';
import { terser } from "rollup-plugin-terser";
import resolve from 'rollup-plugin-node-resolve'
import { terser } from 'rollup-plugin-terser'
const input = 'lib/rough-notation.js';
const input = 'lib/rough-notation.js'
export default [
{
@@ -9,24 +9,24 @@ export default [
output: {
file: 'lib/rough-notation.iife.js',
format: 'iife',
name: 'RoughNotation'
name: 'RoughNotation',
},
plugins: [resolve(), terser()]
plugins: [resolve(), terser()],
},
{
input,
output: {
file: 'lib/rough-notation.esm.js',
format: 'esm'
format: 'esm',
},
plugins: [resolve(), terser()]
plugins: [resolve(), terser()],
},
{
input,
output: {
file: 'lib/rough-notation.cjs.js',
format: 'cjs'
format: 'cjs',
},
plugins: [resolve(), terser()]
plugins: [resolve(), terser()],
},
];
]

View File

@@ -1,4 +1,3 @@
export const SVG_NS = 'http://www.w3.org/2000/svg';
export const DEFAULT_ANIMATION_DURATION = 800;
export const SVG_NS = 'http://www.w3.org/2000/svg'
export const DEFAULT_ANIMATION_DURATION = 800

View File

@@ -1,7 +1,7 @@
export function ensureKeyframes() {
if (!(window as any).__rno_kf_s) {
const style = (window as any).__rno_kf_s = document.createElement('style');
style.textContent = `@keyframes rough-notation-dash { to { stroke-dashoffset: 0; } }`;
document.head.appendChild(style);
const style = (window as any).__rno_kf_s = document.createElement('style')
style.textContent = `@keyframes rough-notation-dash { to { stroke-dashoffset: 0; } }`
document.head.appendChild(style)
}
}

View File

@@ -1,22 +1,21 @@
import type { OpSet, ResolvedOptions } from 'roughjs/bin/core'
import { ellipse, line, linearPath, rectangle } from 'roughjs/bin/renderer'
import { RoughGenerator } from 'roughjs/bin/generator'
import type { Point } from 'roughjs/bin/geometry'
import type { BracketType, FullPadding, Rect, RoughAnnotationConfig } from './types'
import { SVG_NS } from './constants'
import { Rect, RoughAnnotationConfig, FullPadding, BracketType } from "./types";
import { ResolvedOptions, OpSet } from 'roughjs/bin/core';
import { line, rectangle, ellipse, linearPath } from 'roughjs/bin/renderer';
import { RoughGenerator } from 'roughjs/bin/generator';
import { Point } from 'roughjs/bin/geometry';
type RoughOptionsType = 'highlight' | 'single' | 'double';
type RoughOptionsType = 'highlight' | 'single' | 'double'
let defaultOptions: ResolvedOptions | null = null;
let defaultOptions: ResolvedOptions | null = null
function getDefaultOptions(): ResolvedOptions {
if (!defaultOptions) {
const gen = new RoughGenerator();
defaultOptions = gen.defaultOptions;
const gen = new RoughGenerator()
defaultOptions = gen.defaultOptions
}
return defaultOptions;
return defaultOptions
}
function getOptions(type: RoughOptionsType, seed: number): ResolvedOptions {
return {
...getDefaultOptions(),
@@ -38,241 +37,235 @@ function getOptions(type: RoughOptionsType, seed: number): ResolvedOptions {
// combineNestedSvgPaths: false,
disableMultiStroke: type !== 'double',
disableMultiStrokeFill: false,
seed
};
seed,
}
}
function parsePadding(config: RoughAnnotationConfig): FullPadding {
const p = config.padding;
const p = config.padding
if (p || (p === 0)) {
if (typeof p === 'number') {
return [p, p, p, p];
} else if (Array.isArray(p)) {
const pa = p as number[];
return [p, p, p, p]
}
else if (Array.isArray(p)) {
const pa = p as number[]
if (pa.length) {
switch (pa.length) {
case 4:
return [...pa] as FullPadding;
return [...pa] as FullPadding
case 1:
return [pa[0], pa[0], pa[0], pa[0]];
return [pa[0], pa[0], pa[0], pa[0]]
case 2:
return [...pa, ...pa] as FullPadding;
return [...pa, ...pa] as FullPadding
case 3:
return [...pa, pa[1]] as FullPadding;
return [...pa, pa[1]] as FullPadding
default:
return [pa[0], pa[1], pa[2], pa[3]];
return [pa[0], pa[1], pa[2], pa[3]]
}
}
}
}
return [5, 5, 5, 5];
return [5, 5, 5, 5]
}
export function renderAnnotation(svg: SVGSVGElement, rect: Rect, config: RoughAnnotationConfig, animationGroupDelay: number, animationDuration: number, seed: number) {
const opList: OpSet[] = [];
let strokeWidth = config.strokeWidth || 2;
const padding = parsePadding(config);
const animate = (config.animate === undefined) ? true : (!!config.animate);
const iterations = config.iterations || 2;
const rtl = config.rtl ? 1 : 0;
const o = getOptions('single', seed);
const opList: OpSet[] = []
let strokeWidth = config.strokeWidth || 2
const padding = parsePadding(config)
const animate = (config.animate === undefined) ? true : (!!config.animate)
const iterations = config.iterations || 2
const rtl = config.rtl ? 1 : 0
const o = getOptions('single', seed)
switch (config.type) {
case 'underline': {
const y = rect.y + rect.h + padding[2];
const y = rect.y + rect.h + padding[2]
for (let i = rtl; i < iterations + rtl; i++) {
if (i % 2) {
opList.push(line(rect.x + rect.w, y, rect.x, y, o));
} else {
opList.push(line(rect.x, y, rect.x + rect.w, y, o));
}
if (i % 2)
opList.push(line(rect.x + rect.w, y, rect.x, y, o))
else
opList.push(line(rect.x, y, rect.x + rect.w, y, o))
}
break;
break
}
case 'strike-through': {
const y = rect.y + (rect.h / 2);
const y = rect.y + (rect.h / 2)
for (let i = rtl; i < iterations + rtl; i++) {
if (i % 2) {
opList.push(line(rect.x + rect.w, y, rect.x, y, o));
} else {
opList.push(line(rect.x, y, rect.x + rect.w, y, o));
}
if (i % 2)
opList.push(line(rect.x + rect.w, y, rect.x, y, o))
else
opList.push(line(rect.x, y, rect.x + rect.w, y, o))
}
break;
break
}
case 'box': {
const x = rect.x - padding[3];
const y = rect.y - padding[0];
const width = rect.w + (padding[1] + padding[3]);
const height = rect.h + (padding[0] + padding[2]);
for (let i = 0; i < iterations; i++) {
opList.push(rectangle(x, y, width, height, o));
}
break;
const x = rect.x - padding[3]
const y = rect.y - padding[0]
const width = rect.w + (padding[1] + padding[3])
const height = rect.h + (padding[0] + padding[2])
for (let i = 0; i < iterations; i++)
opList.push(rectangle(x, y, width, height, o))
break
}
case 'bracket': {
const brackets: BracketType[] = Array.isArray(config.brackets) ? config.brackets : (config.brackets ? [config.brackets] : ['right']);
const lx = rect.x - padding[3] * 2;
const rx = rect.x + rect.w + padding[1] * 2;
const ty = rect.y - padding[0] * 2;
const by = rect.y + rect.h + padding[2] * 2;
const brackets: BracketType[] = Array.isArray(config.brackets) ? config.brackets : (config.brackets ? [config.brackets] : ['right'])
const lx = rect.x - padding[3] * 2
const rx = rect.x + rect.w + padding[1] * 2
const ty = rect.y - padding[0] * 2
const by = rect.y + rect.h + padding[2] * 2
for (const br of brackets) {
let points: Point[];
let points: Point[]
switch (br) {
case 'bottom':
points = [
[lx, rect.y + rect.h],
[lx, by],
[rx, by],
[rx, rect.y + rect.h]
];
break;
[rx, rect.y + rect.h],
]
break
case 'top':
points = [
[lx, rect.y],
[lx, ty],
[rx, ty],
[rx, rect.y]
];
break;
[rx, rect.y],
]
break
case 'left':
points = [
[rect.x, ty],
[lx, ty],
[lx, by],
[rect.x, by]
];
break;
[rect.x, by],
]
break
case 'right':
points = [
[rect.x + rect.w, ty],
[rx, ty],
[rx, by],
[rect.x + rect.w, by]
];
break;
}
if (points) {
opList.push(linearPath(points, false, o));
[rect.x + rect.w, by],
]
break
}
if (points)
opList.push(linearPath(points, false, o))
}
break;
break
}
case 'crossed-off': {
const x = rect.x;
const y = rect.y;
const x2 = x + rect.w;
const y2 = y + rect.h;
const x = rect.x
const y = rect.y
const x2 = x + rect.w
const y2 = y + rect.h
for (let i = rtl; i < iterations + rtl; i++) {
if (i % 2) {
opList.push(line(x2, y2, x, y, o));
} else {
opList.push(line(x, y, x2, y2, o));
}
if (i % 2)
opList.push(line(x2, y2, x, y, o))
else
opList.push(line(x, y, x2, y2, o))
}
for (let i = rtl; i < iterations + rtl; i++) {
if (i % 2) {
opList.push(line(x, y2, x2, y, o));
} else {
opList.push(line(x2, y, x, y2, o));
}
if (i % 2)
opList.push(line(x, y2, x2, y, o))
else
opList.push(line(x2, y, x, y2, o))
}
break;
break
}
case 'circle': {
const doubleO = getOptions('double', seed);
const width = rect.w + (padding[1] + padding[3]);
const height = rect.h + (padding[0] + padding[2]);
const x = rect.x - padding[3] + (width / 2);
const y = rect.y - padding[0] + (height / 2);
const fullItr = Math.floor(iterations / 2);
const singleItr = iterations - (fullItr * 2);
for (let i = 0; i < fullItr; i++) {
opList.push(ellipse(x, y, width, height, doubleO));
}
for (let i = 0; i < singleItr; i++) {
opList.push(ellipse(x, y, width, height, o));
}
break;
const doubleO = getOptions('double', seed)
const width = rect.w + (padding[1] + padding[3])
const height = rect.h + (padding[0] + padding[2])
const x = rect.x - padding[3] + (width / 2)
const y = rect.y - padding[0] + (height / 2)
const fullItr = Math.floor(iterations / 2)
const singleItr = iterations - (fullItr * 2)
for (let i = 0; i < fullItr; i++)
opList.push(ellipse(x, y, width, height, doubleO))
for (let i = 0; i < singleItr; i++)
opList.push(ellipse(x, y, width, height, o))
break
}
case 'highlight': {
const o = getOptions('highlight', seed);
strokeWidth = rect.h * 0.95;
const y = rect.y + (rect.h / 2);
const o = getOptions('highlight', seed)
strokeWidth = rect.h * 0.95
const y = rect.y + (rect.h / 2)
for (let i = rtl; i < iterations + rtl; i++) {
if (i % 2) {
opList.push(line(rect.x + rect.w, y, rect.x, y, o));
} else {
opList.push(line(rect.x, y, rect.x + rect.w, y, o));
}
if (i % 2)
opList.push(line(rect.x + rect.w, y, rect.x, y, o))
else
opList.push(line(rect.x, y, rect.x + rect.w, y, o))
}
break;
break
}
}
if (opList.length) {
const pathStrings = opsToPath(opList);
const lengths: number[] = [];
const pathElements: SVGPathElement[] = [];
let totalLength = 0;
const setAttr = (p: SVGPathElement, an: string, av: string) => p.setAttribute(an, av);
const pathStrings = opsToPath(opList)
const lengths: number[] = []
const pathElements: SVGPathElement[] = []
let totalLength = 0
const setAttr = (p: SVGPathElement, an: string, av: string) => p.setAttribute(an, av)
for (const d of pathStrings) {
const path = document.createElementNS(SVG_NS, 'path');
setAttr(path, 'd', d);
setAttr(path, 'fill', 'none');
setAttr(path, 'stroke', config.color || 'currentColor');
setAttr(path, 'stroke-width', `${strokeWidth}`);
const path = document.createElementNS(SVG_NS, 'path')
setAttr(path, 'd', d)
setAttr(path, 'fill', 'none')
setAttr(path, 'stroke', config.color || 'currentColor')
setAttr(path, 'stroke-width', `${strokeWidth}`)
if (animate) {
const length = path.getTotalLength();
lengths.push(length);
totalLength += length;
const length = path.getTotalLength()
lengths.push(length)
totalLength += length
}
svg.appendChild(path);
pathElements.push(path);
svg.appendChild(path)
pathElements.push(path)
}
if (animate) {
let durationOffset = 0;
let durationOffset = 0
for (let i = 0; i < pathElements.length; i++) {
const path = pathElements[i];
const length = lengths[i];
const duration = totalLength ? (animationDuration * (length / totalLength)) : 0;
const delay = animationGroupDelay + durationOffset;
const style = path.style;
style.strokeDashoffset = `${length}`;
style.strokeDasharray = `${length}`;
style.animation = `rough-notation-dash ${duration}ms ease-out ${delay}ms forwards`;
durationOffset += duration;
const path = pathElements[i]
const length = lengths[i]
const duration = totalLength ? (animationDuration * (length / totalLength)) : 0
const delay = animationGroupDelay + durationOffset
const style = path.style
style.strokeDashoffset = `${length}`
style.strokeDasharray = `${length}`
style.animation = `rough-notation-dash ${duration}ms ease-out ${delay}ms forwards`
durationOffset += duration
}
}
}
}
function opsToPath(opList: OpSet[]): string[] {
const paths: string[] = [];
const paths: string[] = []
for (const drawing of opList) {
let path = '';
let path = ''
for (const item of drawing.ops) {
const data = item.data;
const data = item.data
switch (item.op) {
case 'move':
if (path.trim()) {
paths.push(path.trim());
}
path = `M${data[0]} ${data[1]} `;
break;
if (path.trim())
paths.push(path.trim())
path = `M${data[0]} ${data[1]} `
break
case 'bcurveTo':
path += `C${data[0]} ${data[1]}, ${data[2]} ${data[3]}, ${data[4]} ${data[5]} `;
break;
path += `C${data[0]} ${data[1]}, ${data[2]} ${data[3]}, ${data[4]} ${data[5]} `
break
case 'lineTo':
path += `L${data[0]} ${data[1]} `;
break;
path += `L${data[0]} ${data[1]} `
break
}
}
if (path.trim()) {
paths.push(path.trim());
}
if (path.trim())
paths.push(path.trim())
}
return paths;
return paths
}

View File

@@ -1,288 +1,279 @@
import { SVG_NS, DEFAULT_ANIMATION_DURATION } from './constants';
import { Rect, RoughAnnotationConfig, RoughAnnotation, RoughAnnotationGroup, AnnotationState } from "./types";
import { renderAnnotation } from './render.js';
import { ensureKeyframes } from './keyframes.js';
import { randomSeed } from 'roughjs/bin/math';
import { randomSeed } from 'roughjs/bin/math'
import { DEFAULT_ANIMATION_DURATION, SVG_NS } from './constants'
import type { AnnotationState, Rect, RoughAnnotation, RoughAnnotationConfig, RoughAnnotationGroup } from './types'
import { renderAnnotation } from './render.js'
import { ensureKeyframes } from './keyframes.js'
class RoughAnnotationImpl implements RoughAnnotation {
private _state: AnnotationState = 'unattached';
private _config: RoughAnnotationConfig;
private _resizing = false;
private _ro?: any; // ResizeObserver is not supported in typescript std lib yet
private _seed = randomSeed();
private _state: AnnotationState = 'unattached'
private _config: RoughAnnotationConfig
private _resizing = false
private _ro?: any // ResizeObserver is not supported in typescript std lib yet
private _seed = randomSeed()
private _e: HTMLElement;
private _svg?: SVGSVGElement;
private _lastSizes: Rect[] = [];
private _e: HTMLElement
private _svg?: SVGSVGElement
private _lastSizes: Rect[] = []
_animationDelay = 0;
_animationDelay = 0
constructor(e: HTMLElement, config: RoughAnnotationConfig) {
this._e = e;
this._config = JSON.parse(JSON.stringify(config));
this.attach();
this._e = e
this._config = JSON.parse(JSON.stringify(config))
this.attach()
}
get animate() { return this._config.animate; }
set animate(value) { this._config.animate = value; }
get animate() { return this._config.animate }
set animate(value) { this._config.animate = value }
get animationDuration() { return this._config.animationDuration; }
set animationDuration(value) { this._config.animationDuration = value; }
get animationDuration() { return this._config.animationDuration }
set animationDuration(value) { this._config.animationDuration = value }
get iterations() { return this._config.iterations; }
set iterations(value) { this._config.iterations = value; }
get iterations() { return this._config.iterations }
set iterations(value) { this._config.iterations = value }
get color() { return this._config.color; }
get color() { return this._config.color }
set color(value) {
if (this._config.color !== value) {
this._config.color = value;
this.refresh();
this._config.color = value
this.refresh()
}
}
get class() { return this._config.class; }
get class() { return this._config.class }
set class(value) {
if (this._config.class !== value) {
this._config.class = value;
if (this._svg) {
this._svg.setAttribute('class', ['rough-annotation', this._config.class || ''].filter(Boolean).join(' '));
}
this._config.class = value
if (this._svg)
this._svg.setAttribute('class', ['rough-annotation', this._config.class || ''].filter(Boolean).join(' '))
}
}
get strokeWidth() { return this._config.strokeWidth; }
get strokeWidth() { return this._config.strokeWidth }
set strokeWidth(value) {
if (this._config.strokeWidth !== value) {
this._config.strokeWidth = value;
this.refresh();
this._config.strokeWidth = value
this.refresh()
}
}
get padding() { return this._config.padding; }
get padding() { return this._config.padding }
set padding(value) {
if (this._config.padding !== value) {
this._config.padding = value;
this.refresh();
this._config.padding = value
this.refresh()
}
}
private _resizeListener = () => {
if (!this._resizing) {
this._resizing = true;
this._resizing = true
setTimeout(() => {
this._resizing = false;
this._resizing = false
if (this._state === 'showing') {
if (this.haveRectsChanged()) {
this.show();
}
if (this.haveRectsChanged())
this.show()
}
}, 400);
}, 400)
}
}
private attach() {
if (this._state === 'unattached' && this._e.parentElement) {
ensureKeyframes();
const svg = this._svg = document.createElementNS(SVG_NS, 'svg');
svg.setAttribute('class', ['rough-annotation', this._config.class || ''].filter(Boolean).join(' '));
const style = svg.style;
style.position = 'absolute';
style.top = '0';
style.left = '0';
style.overflow = 'visible';
style.pointerEvents = 'none';
style.width = '100px';
style.height = '100px';
const prepend = this._config.type === 'highlight';
this._e.insertAdjacentElement(prepend ? 'beforebegin' : 'afterend', svg);
this._state = 'not-showing';
ensureKeyframes()
const svg = this._svg = document.createElementNS(SVG_NS, 'svg')
svg.setAttribute('class', ['rough-annotation', this._config.class || ''].filter(Boolean).join(' '))
const style = svg.style
style.position = 'absolute'
style.top = '0'
style.left = '0'
style.overflow = 'visible'
style.pointerEvents = 'none'
style.width = '100px'
style.height = '100px'
const prepend = this._config.type === 'highlight'
this._e.insertAdjacentElement(prepend ? 'beforebegin' : 'afterend', svg)
this._state = 'not-showing'
// ensure e is positioned
if (prepend) {
const computedPos = window.getComputedStyle(this._e).position;
const unpositioned = (!computedPos) || (computedPos === 'static');
if (unpositioned) {
this._e.style.position = 'relative';
}
const computedPos = window.getComputedStyle(this._e).position
const unpositioned = (!computedPos) || (computedPos === 'static')
if (unpositioned)
this._e.style.position = 'relative'
}
this.attachListeners();
this.attachListeners()
}
}
private detachListeners() {
window.removeEventListener('resize', this._resizeListener);
if (this._ro) {
this._ro.unobserve(this._e);
}
window.removeEventListener('resize', this._resizeListener)
if (this._ro)
this._ro.unobserve(this._e)
}
private attachListeners() {
this.detachListeners();
window.addEventListener('resize', this._resizeListener, { passive: true });
this.detachListeners()
window.addEventListener('resize', this._resizeListener, { passive: true })
if ((!this._ro) && ('ResizeObserver' in window)) {
this._ro = new (window as any).ResizeObserver((entries: any) => {
for (const entry of entries) {
if (entry.contentRect) {
this._resizeListener();
}
if (entry.contentRect)
this._resizeListener()
}
});
}
if (this._ro) {
this._ro.observe(this._e);
})
}
if (this._ro)
this._ro.observe(this._e)
}
private haveRectsChanged(): boolean {
if (this._lastSizes.length) {
const newRects = this.rects();
const newRects = this.rects()
if (newRects.length === this._lastSizes.length) {
for (let i = 0; i < newRects.length; i++) {
if (!this.isSameRect(newRects[i], this._lastSizes[i])) {
return true;
}
if (!this.isSameRect(newRects[i], this._lastSizes[i]))
return true
}
} else {
return true;
}
else {
return true
}
}
return false;
return false
}
private isSameRect(rect1: Rect, rect2: Rect): boolean {
const si = (a: number, b: number) => Math.round(a) === Math.round(b);
const si = (a: number, b: number) => Math.round(a) === Math.round(b)
return (
si(rect1.x, rect2.x) &&
si(rect1.y, rect2.y) &&
si(rect1.w, rect2.w) &&
si(rect1.h, rect2.h)
);
si(rect1.x, rect2.x)
&& si(rect1.y, rect2.y)
&& si(rect1.w, rect2.w)
&& si(rect1.h, rect2.h)
)
}
isShowing(): boolean {
return (this._state !== 'not-showing');
return (this._state !== 'not-showing')
}
private pendingRefresh?: Promise<void>;
private pendingRefresh?: Promise<void>
private refresh() {
if (this.isShowing() && (!this.pendingRefresh)) {
this.pendingRefresh = Promise.resolve().then(() => {
if (this.isShowing()) {
this.show();
}
delete this.pendingRefresh;
});
if (this.isShowing())
this.show()
delete this.pendingRefresh
})
}
}
show(): void {
switch (this._state) {
case 'unattached':
break;
break
case 'showing':
this.hide();
if (this._svg) {
this.render(this._svg, true);
}
break;
this.hide()
if (this._svg)
this.render(this._svg, true)
break
case 'not-showing':
this.attach();
if (this._svg) {
this.render(this._svg, false);
}
break;
this.attach()
if (this._svg)
this.render(this._svg, false)
break
}
}
hide(): void {
if (this._svg) {
while (this._svg.lastChild) {
this._svg.removeChild(this._svg.lastChild);
}
while (this._svg.lastChild)
this._svg.removeChild(this._svg.lastChild)
}
this._state = 'not-showing';
this._state = 'not-showing'
}
remove(): void {
if (this._svg && this._svg.parentElement) {
this._svg.parentElement.removeChild(this._svg);
}
this._svg = undefined;
this._state = 'unattached';
this.detachListeners();
if (this._svg && this._svg.parentElement)
this._svg.parentElement.removeChild(this._svg)
this._svg = undefined
this._state = 'unattached'
this.detachListeners()
}
private render(svg: SVGSVGElement, ensureNoAnimation: boolean) {
let config = this._config;
let config = this._config
if (ensureNoAnimation) {
config = JSON.parse(JSON.stringify(this._config));
config.animate = false;
config = JSON.parse(JSON.stringify(this._config))
config.animate = false
}
const rects = this.rects();
let totalWidth = 0;
rects.forEach((rect) => totalWidth += rect.w);
const totalDuration = (config.animationDuration || DEFAULT_ANIMATION_DURATION);
let delay = 0;
const rects = this.rects()
let totalWidth = 0
rects.forEach(rect => totalWidth += rect.w)
const totalDuration = (config.animationDuration || DEFAULT_ANIMATION_DURATION)
let delay = 0
for (let i = 0; i < rects.length; i++) {
const rect = rects[i];
const ad = totalDuration * (rect.w / totalWidth);
renderAnnotation(svg, rects[i], config, delay + this._animationDelay, ad, this._seed);
delay += ad;
const rect = rects[i]
const ad = totalDuration * (rect.w / totalWidth)
renderAnnotation(svg, rects[i], config, delay + this._animationDelay, ad, this._seed)
delay += ad
}
this._lastSizes = rects;
this._state = 'showing';
this._lastSizes = rects
this._state = 'showing'
}
private rects(): Rect[] {
const ret: Rect[] = [];
const ret: Rect[] = []
if (this._svg) {
if (this._config.multiline) {
const elementRects = this._e.getClientRects();
for (let i = 0; i < elementRects.length; i++) {
ret.push(this.svgRect(this._svg, elementRects[i]));
}
} else {
ret.push(this.svgRect(this._svg, this._e.getBoundingClientRect()));
const elementRects = this._e.getClientRects()
for (let i = 0; i < elementRects.length; i++)
ret.push(this.svgRect(this._svg, elementRects[i]))
}
else {
ret.push(this.svgRect(this._svg, this._e.getBoundingClientRect()))
}
}
return ret;
return ret
}
private svgRect(svg: SVGSVGElement, bounds: DOMRect | DOMRectReadOnly): Rect {
const rect1 = svg.getBoundingClientRect();
const rect2 = bounds;
const rect1 = svg.getBoundingClientRect()
const rect2 = bounds
return {
x: (rect2.x || rect2.left) - (rect1.x || rect1.left),
y: (rect2.y || rect2.top) - (rect1.y || rect1.top),
w: rect2.width,
h: rect2.height
};
h: rect2.height,
}
}
}
export function annotate(element: HTMLElement, config: RoughAnnotationConfig): RoughAnnotation {
return new RoughAnnotationImpl(element, config);
return new RoughAnnotationImpl(element, config)
}
export function annotationGroup(annotations: RoughAnnotation[]): RoughAnnotationGroup {
let delay = 0;
let delay = 0
for (const a of annotations) {
const ai = a as RoughAnnotationImpl;
ai._animationDelay = delay;
const duration = ai.animationDuration === 0 ? 0 : (ai.animationDuration || DEFAULT_ANIMATION_DURATION);
delay += duration;
const ai = a as RoughAnnotationImpl
ai._animationDelay = delay
const duration = ai.animationDuration === 0 ? 0 : (ai.animationDuration || DEFAULT_ANIMATION_DURATION)
delay += duration
}
const list = [...annotations];
const list = [...annotations]
return {
show() {
for (const a of list) {
a.show();
}
for (const a of list)
a.show()
},
hide() {
for (const a of list) {
a.hide();
}
}
};
for (const a of list)
a.hide()
},
}
}

View File

@@ -1,43 +1,43 @@
export interface Rect {
x: number;
y: number;
w: number;
h: number;
x: number
y: number
w: number
h: number
}
export type RoughAnnotationType = 'underline' | 'box' | 'circle' | 'highlight' | 'strike-through' | 'crossed-off' | 'bracket';
export type FullPadding = [number, number, number, number];
export type RoughPadding = number | [number, number] | FullPadding;
export type BracketType = 'left' | 'right' | 'top' | 'bottom';
export type RoughAnnotationType = 'underline' | 'box' | 'circle' | 'highlight' | 'strike-through' | 'crossed-off' | 'bracket'
export type FullPadding = [number, number, number, number]
export type RoughPadding = number | [number, number] | FullPadding
export type BracketType = 'left' | 'right' | 'top' | 'bottom'
export interface RoughAnnotationConfig extends RoughAnnotationConfigBase {
type: RoughAnnotationType;
multiline?: boolean;
rtl?: boolean;
type: RoughAnnotationType
multiline?: boolean
rtl?: boolean
}
export interface RoughAnnotationConfigBase {
animate?: boolean; // defaults to true
animationDuration?: number; // defaults to 1000ms
color?: string; // defaults to currentColor
strokeWidth?: number; // default based on type
padding?: RoughPadding; // defaults to 5px
iterations?: number; // defaults to 2
brackets?: BracketType | BracketType[]; // defaults to 'right'
animate?: boolean // defaults to true
animationDuration?: number // defaults to 1000ms
color?: string // defaults to currentColor
strokeWidth?: number // default based on type
padding?: RoughPadding // defaults to 5px
iterations?: number // defaults to 2
brackets?: BracketType | BracketType[] // defaults to 'right'
// Additional class added to the annotation
class?: string
}
export interface RoughAnnotation extends RoughAnnotationConfigBase {
isShowing(): boolean;
show(): void;
hide(): void;
remove(): void;
isShowing: () => boolean
show: () => void
hide: () => void
remove: () => void
}
export interface RoughAnnotationGroup {
show(): void;
hide(): void;
show: () => void
hide: () => void
}
export type AnnotationState = 'unattached' | 'not-showing' | 'showing';
export type AnnotationState = 'unattached' | 'not-showing' | 'showing'

View File

@@ -1,22 +1,22 @@
{
"compilerOptions": {
"target": "es2017",
"module": "es2015",
"moduleResolution": "node",
"lib": [
"es2017",
"dom"
],
"declaration": true,
"outDir": "./lib",
"baseUrl": ".",
"module": "es2015",
"moduleResolution": "node",
"strict": true,
"strictNullChecks": true,
"noFallthroughCasesInSwitch": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
"declaration": true,
"outDir": "./lib"
},
"include": [
"src/**/*.ts"

View File

@@ -1,61 +0,0 @@
{
"rules": {
"arrow-parens": true,
"class-name": true,
"indent": [
true,
"spaces",
2
],
"prefer-const": true,
"no-duplicate-variable": true,
"no-eval": true,
"no-internal-module": true,
"no-trailing-whitespace": false,
"no-var-keyword": true,
"one-line": [
true,
"check-open-brace",
"check-whitespace"
],
"quotemark": [
true,
"single",
"avoid-escape"
],
"semicolon": [
true,
"always"
],
"trailing-comma": [
true,
"multiline"
],
"triple-equals": [
true,
"allow-null-check"
],
"typedef-whitespace": [
true,
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
}
],
"variable-name": [
true,
"ban-keywords"
],
"whitespace": [
true,
"check-branch",
"check-decl",
"check-operator",
"check-separator",
"check-type"
]
}
}