@operato/image-marker 8.0.0-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +29 -0
- package/.storybook/main.js +3 -0
- package/.storybook/preview.js +52 -0
- package/.storybook/server.mjs +8 -0
- package/CHANGELOG.md +11 -0
- package/README.md +0 -0
- package/assets/images/no-image.png +0 -0
- package/demo/index.html +22 -0
- package/dist/src/base-image-marker.d.ts +24 -0
- package/dist/src/base-image-marker.js +137 -0
- package/dist/src/base-image-marker.js.map +1 -0
- package/dist/src/index.d.ts +0 -0
- package/dist/src/index.js +2 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/ox-image-marker-mode.d.ts +13 -0
- package/dist/src/ox-image-marker-mode.js +140 -0
- package/dist/src/ox-image-marker-mode.js.map +1 -0
- package/dist/src/ox-image-marker-view.d.ts +4 -0
- package/dist/src/ox-image-marker-view.js +26 -0
- package/dist/src/ox-image-marker-view.js.map +1 -0
- package/dist/src/ox-image-marker.d.ts +33 -0
- package/dist/src/ox-image-marker.js +583 -0
- package/dist/src/ox-image-marker.js.map +1 -0
- package/dist/src/types.d.ts +20 -0
- package/dist/src/types.js +2 -0
- package/dist/src/types.js.map +1 -0
- package/dist/stories/ox-image-marker-mode.stories.d.ts +16 -0
- package/dist/stories/ox-image-marker-mode.stories.js +31 -0
- package/dist/stories/ox-image-marker-mode.stories.js.map +1 -0
- package/dist/stories/ox-image-marker-view.stories.d.ts +41 -0
- package/dist/stories/ox-image-marker-view.stories.js +103 -0
- package/dist/stories/ox-image-marker-view.stories.js.map +1 -0
- package/dist/stories/ox-image-marker.stories.d.ts +33 -0
- package/dist/stories/ox-image-marker.stories.js +86 -0
- package/dist/stories/ox-image-marker.stories.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +97 -0
- package/src/base-image-marker.ts +144 -0
- package/src/index.ts +0 -0
- package/src/ox-image-marker-mode.ts +145 -0
- package/src/ox-image-marker-view.ts +23 -0
- package/src/ox-image-marker.ts +626 -0
- package/src/types.ts +20 -0
- package/stories/ox-image-marker-mode.stories.ts +41 -0
- package/stories/ox-image-marker-view.stories.ts +134 -0
- package/stories/ox-image-marker.stories.ts +102 -0
- package/themes/app-theme.css +138 -0
- package/themes/calendar-theme.css +59 -0
- package/themes/dark.css +51 -0
- package/themes/form-theme.css +70 -0
- package/themes/grist-theme.css +175 -0
- package/themes/layout-theme.css +94 -0
- package/themes/light.css +51 -0
- package/themes/material-theme.css +23 -0
- package/themes/md-typescale-styles.css +100 -0
- package/themes/oops-theme.css +22 -0
- package/themes/report-theme.css +47 -0
- package/themes/spacing.css +23 -0
- package/themes/state-color.css +6 -0
- package/themes/tooltip-theme.css +11 -0
- package/translations/en.json +1 -0
- package/translations/ja.json +1 -0
- package/translations/ko.json +1 -0
- package/translations/ms.json +1 -0
- package/translations/zh.json +1 -0
- package/tsconfig.json +25 -0
- package/web-dev-server.config.mjs +27 -0
- package/web-test-runner.config.mjs +41 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { LitElement, css, html, svg, nothing } from 'lit'
|
|
2
|
+
import { property, state, query } from 'lit/decorators.js'
|
|
3
|
+
|
|
4
|
+
import { Shape } from './types'
|
|
5
|
+
|
|
6
|
+
export class BaseImageMarker extends LitElement {
|
|
7
|
+
static styles = [
|
|
8
|
+
css`
|
|
9
|
+
:host {
|
|
10
|
+
display: flex;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.container {
|
|
14
|
+
flex: 1;
|
|
15
|
+
position: relative;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
svg {
|
|
19
|
+
position: absolute;
|
|
20
|
+
top: 0;
|
|
21
|
+
left: 0;
|
|
22
|
+
cursor: pointer;
|
|
23
|
+
width: 100%;
|
|
24
|
+
height: 100%;
|
|
25
|
+
}
|
|
26
|
+
`
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
@property({ type: String }) imageUrl: string = ''
|
|
30
|
+
@property({ type: Array }) shapes: Shape[] = []
|
|
31
|
+
|
|
32
|
+
@state() protected viewBox = '0 0 800 600'
|
|
33
|
+
@state() protected zoomLevel = 1
|
|
34
|
+
|
|
35
|
+
protected zoomFactor = 0.1
|
|
36
|
+
protected isPanning = false
|
|
37
|
+
protected panStart = { x: 0, y: 0 }
|
|
38
|
+
protected initialViewBox = '0 0 800 600'
|
|
39
|
+
|
|
40
|
+
@query('svg') protected svg!: SVGSVGElement
|
|
41
|
+
|
|
42
|
+
protected onImageLoad(event: Event) {
|
|
43
|
+
const img = new Image()
|
|
44
|
+
img.onload = () => {
|
|
45
|
+
const width = img.naturalWidth
|
|
46
|
+
const height = img.naturalHeight
|
|
47
|
+
this.viewBox = `0 0 ${width} ${height}`
|
|
48
|
+
this.initialViewBox = `0 0 ${width} ${height}`
|
|
49
|
+
}
|
|
50
|
+
img.src = this.imageUrl
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
protected onWheel(event: WheelEvent) {
|
|
54
|
+
event.preventDefault()
|
|
55
|
+
|
|
56
|
+
const { offsetX, offsetY, deltaY } = event
|
|
57
|
+
const scale = deltaY > 0 ? 1 - this.zoomFactor : 1 + this.zoomFactor
|
|
58
|
+
this.zoomLevel *= scale
|
|
59
|
+
|
|
60
|
+
const [vbX, vbY, vbWidth, vbHeight] = this.viewBox.split(' ').map(Number)
|
|
61
|
+
const mouseX = vbX + (offsetX / this.svg.clientWidth) * vbWidth
|
|
62
|
+
const mouseY = vbY + (offsetY / this.svg.clientHeight) * vbHeight
|
|
63
|
+
|
|
64
|
+
const newVbWidth = vbWidth / scale
|
|
65
|
+
const newVbHeight = vbHeight / scale
|
|
66
|
+
|
|
67
|
+
const newVbX = mouseX - (offsetX / this.svg.clientWidth) * newVbWidth
|
|
68
|
+
const newVbY = mouseY - (offsetY / this.svg.clientHeight) * newVbHeight
|
|
69
|
+
|
|
70
|
+
this.viewBox = `${newVbX} ${newVbY} ${newVbWidth} ${newVbHeight}`
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
protected onSvgMouseDown(event: MouseEvent) {
|
|
74
|
+
const svg = this.svg
|
|
75
|
+
|
|
76
|
+
this.isPanning = true
|
|
77
|
+
this.panStart = { x: event.clientX, y: event.clientY }
|
|
78
|
+
svg.classList.add('dragging')
|
|
79
|
+
|
|
80
|
+
svg.addEventListener('mousemove', this.onPanMouseMove)
|
|
81
|
+
svg.addEventListener('mouseup', this.onPanMouseUp)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
protected onPanMouseMove = (event: MouseEvent) => {
|
|
85
|
+
if (!this.isPanning) return
|
|
86
|
+
|
|
87
|
+
const dx = event.clientX - this.panStart.x
|
|
88
|
+
const dy = event.clientY - this.panStart.y
|
|
89
|
+
|
|
90
|
+
const [vbX, vbY, vbWidth, vbHeight] = this.viewBox.split(' ').map(Number)
|
|
91
|
+
const scaleX = vbWidth / this.svg.clientWidth
|
|
92
|
+
const scaleY = vbHeight / this.svg.clientHeight
|
|
93
|
+
const scale = Math.max(scaleX, scaleY)
|
|
94
|
+
|
|
95
|
+
const newVbX = vbX - dx * scale
|
|
96
|
+
const newVbY = vbY - dy * scale
|
|
97
|
+
|
|
98
|
+
this.viewBox = `${newVbX} ${newVbY} ${vbWidth} ${vbHeight}`
|
|
99
|
+
this.panStart = { x: event.clientX, y: event.clientY }
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
protected onPanMouseUp = () => {
|
|
103
|
+
const svg = this.svg
|
|
104
|
+
|
|
105
|
+
this.isPanning = false
|
|
106
|
+
svg.classList.remove('dragging')
|
|
107
|
+
svg.removeEventListener('mousemove', this.onPanMouseMove)
|
|
108
|
+
svg.removeEventListener('mouseup', this.onPanMouseUp)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
protected onDoubleClick() {
|
|
112
|
+
this.viewBox = this.initialViewBox
|
|
113
|
+
this.zoomLevel = 1
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
protected renderShape(shape: Shape) {
|
|
117
|
+
const commonAttrs = {
|
|
118
|
+
stroke: 'red',
|
|
119
|
+
'stroke-width': 1,
|
|
120
|
+
fill: 'transparent'
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
switch (shape.type) {
|
|
124
|
+
case 'text':
|
|
125
|
+
return svg`<text x="${shape.x}" y="${shape.y}" text-anchor="middle" alignment-baseline="middle" fill="black">${shape.text}</text>`
|
|
126
|
+
case 'rectangle':
|
|
127
|
+
return svg`<rect x="${shape.x}" y="${shape.y}" width="${shape.width}" height="${shape.height}" stroke="${commonAttrs.stroke}" stroke-width="${commonAttrs['stroke-width']}" fill="${commonAttrs.fill}"></rect>`
|
|
128
|
+
case 'circle':
|
|
129
|
+
return svg`<circle cx="${shape.cx}" cy="${shape.cy}" r="${shape.r}" stroke="${commonAttrs.stroke}" stroke-width="${commonAttrs['stroke-width']}" fill="${commonAttrs.fill}"></circle>`
|
|
130
|
+
case 'line':
|
|
131
|
+
return svg`<line x1="${shape.x1}" y1="${shape.y1}" x2="${shape.x2}" y2="${shape.y2}" stroke="${commonAttrs.stroke}" stroke-width="${commonAttrs['stroke-width']}"></line>`
|
|
132
|
+
case 'arrow':
|
|
133
|
+
return svg`
|
|
134
|
+
<defs>
|
|
135
|
+
<marker id="arrowhead-${shape.id}" markerWidth="10" markerHeight="7" refX="10" refY="3.5" orient="auto">
|
|
136
|
+
<polygon points="0 0, 10 3.5, 0 7" fill="${commonAttrs.stroke}"></polygon>
|
|
137
|
+
</marker>
|
|
138
|
+
</defs>
|
|
139
|
+
<line x1="${shape.x1}" y1="${shape.y1}" x2="${shape.x2}" y2="${shape.y2}" stroke="${commonAttrs.stroke}" stroke-width="${commonAttrs['stroke-width']}" marker-end="url(#arrowhead-${shape.id})"></line>`
|
|
140
|
+
default:
|
|
141
|
+
return nothing
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
package/src/index.ts
ADDED
|
File without changes
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import '@material/web/icon/icon.js'
|
|
2
|
+
|
|
3
|
+
import { LitElement, html, css } from 'lit'
|
|
4
|
+
import { customElement, property } from 'lit/decorators.js'
|
|
5
|
+
import { ImageMarkerMode, ShapeType } from './types'
|
|
6
|
+
|
|
7
|
+
@customElement('ox-image-marker-mode')
|
|
8
|
+
export class OxImageMarkerMode extends LitElement {
|
|
9
|
+
static styles = css`
|
|
10
|
+
:host {
|
|
11
|
+
display: flex;
|
|
12
|
+
flex-direction: column;
|
|
13
|
+
gap: var(--spacing-medium);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.palette {
|
|
17
|
+
display: flex;
|
|
18
|
+
flex-direction: column;
|
|
19
|
+
gap: var(--spacing-small);
|
|
20
|
+
justify-content: center;
|
|
21
|
+
margin: var(--spacing-medium);
|
|
22
|
+
|
|
23
|
+
--md-icon-size: 16px;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.palette > * {
|
|
27
|
+
padding: var(--spacing-small);
|
|
28
|
+
cursor: pointer;
|
|
29
|
+
background-color: #f0f0f0;
|
|
30
|
+
border: 1px solid #ccc;
|
|
31
|
+
border-radius: 4px;
|
|
32
|
+
width: 12px;
|
|
33
|
+
height: 12px;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.palette [selected] {
|
|
37
|
+
background-color: #d0d0d0;
|
|
38
|
+
}
|
|
39
|
+
`
|
|
40
|
+
|
|
41
|
+
@property({ type: String }) currentMode: ImageMarkerMode = 'select'
|
|
42
|
+
@property({ type: String }) currentShapeType?: ShapeType
|
|
43
|
+
|
|
44
|
+
render() {
|
|
45
|
+
return html`
|
|
46
|
+
<div class="palette">
|
|
47
|
+
<md-icon
|
|
48
|
+
title="Move or Select"
|
|
49
|
+
?selected=${this.currentMode !== 'draw'}
|
|
50
|
+
@click=${() => this.setMode(this.currentMode !== 'select' ? 'select' : 'move')}
|
|
51
|
+
>${this.currentMode !== 'move' ? 'select' : 'drag_pan'}</md-icon
|
|
52
|
+
>
|
|
53
|
+
|
|
54
|
+
<md-icon
|
|
55
|
+
title="Text"
|
|
56
|
+
?selected=${this.currentMode === 'draw' && this.currentShapeType === 'text'}
|
|
57
|
+
@click=${() => this.setShape('text')}
|
|
58
|
+
>match_case</md-icon
|
|
59
|
+
>
|
|
60
|
+
|
|
61
|
+
<md-icon
|
|
62
|
+
title="Rectangle"
|
|
63
|
+
?selected=${this.currentMode === 'draw' && this.currentShapeType === 'rectangle'}
|
|
64
|
+
@click=${() => this.setShape('rectangle')}
|
|
65
|
+
>rectangle</md-icon
|
|
66
|
+
>
|
|
67
|
+
|
|
68
|
+
<md-icon
|
|
69
|
+
title="Circle"
|
|
70
|
+
?selected=${this.currentMode === 'draw' && this.currentShapeType === 'circle'}
|
|
71
|
+
@click=${() => this.setShape('circle')}
|
|
72
|
+
>circle</md-icon
|
|
73
|
+
>
|
|
74
|
+
|
|
75
|
+
<md-icon
|
|
76
|
+
title="Triangle"
|
|
77
|
+
?selected=${this.currentMode === 'draw' && this.currentShapeType === 'triangle'}
|
|
78
|
+
@click=${() => this.setShape('triangle')}
|
|
79
|
+
>change_history</md-icon
|
|
80
|
+
>
|
|
81
|
+
|
|
82
|
+
<md-icon
|
|
83
|
+
title="Line"
|
|
84
|
+
?selected=${this.currentMode === 'draw' && this.currentShapeType === 'line'}
|
|
85
|
+
@click=${() => this.setShape('line')}
|
|
86
|
+
>shape_line</md-icon
|
|
87
|
+
>
|
|
88
|
+
|
|
89
|
+
<md-icon
|
|
90
|
+
title="Arrow"
|
|
91
|
+
?selected=${this.currentMode === 'draw' && this.currentShapeType === 'arrow'}
|
|
92
|
+
@click=${() => this.setShape('arrow')}
|
|
93
|
+
>line_end_arrow</md-icon
|
|
94
|
+
>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
<div class="palette">
|
|
98
|
+
<md-icon title="Delete" @click=${() => this.onClickDelete()}>delete</md-icon>
|
|
99
|
+
<md-icon title="Save" @click=${() => this.onClickSave()}>save</md-icon>
|
|
100
|
+
</div>
|
|
101
|
+
`
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private setMode(mode: ImageMarkerMode) {
|
|
105
|
+
this.currentMode = mode
|
|
106
|
+
|
|
107
|
+
this.dispatchEvent(
|
|
108
|
+
new CustomEvent('mode-changed', {
|
|
109
|
+
detail: { mode: this.currentMode, shapeType: this.currentShapeType },
|
|
110
|
+
bubbles: true,
|
|
111
|
+
composed: true
|
|
112
|
+
})
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private setShape(shapeType: ShapeType) {
|
|
117
|
+
this.currentMode = 'draw'
|
|
118
|
+
this.currentShapeType = shapeType
|
|
119
|
+
this.dispatchEvent(
|
|
120
|
+
new CustomEvent('mode-changed', {
|
|
121
|
+
detail: { mode: 'draw', shapeType: this.currentShapeType },
|
|
122
|
+
bubbles: true,
|
|
123
|
+
composed: true
|
|
124
|
+
})
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private onClickDelete() {
|
|
129
|
+
this.dispatchEvent(
|
|
130
|
+
new CustomEvent('clicked-delete', {
|
|
131
|
+
bubbles: true,
|
|
132
|
+
composed: true
|
|
133
|
+
})
|
|
134
|
+
)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private onClickSave() {
|
|
138
|
+
this.dispatchEvent(
|
|
139
|
+
new CustomEvent('clicked-save', {
|
|
140
|
+
bubbles: true,
|
|
141
|
+
composed: true
|
|
142
|
+
})
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { html } from 'lit'
|
|
2
|
+
import { customElement } from 'lit/decorators.js'
|
|
3
|
+
|
|
4
|
+
import { BaseImageMarker } from './base-image-marker.js'
|
|
5
|
+
|
|
6
|
+
@customElement('ox-image-marker-view')
|
|
7
|
+
export class OxImageMarkerView extends BaseImageMarker {
|
|
8
|
+
render() {
|
|
9
|
+
return html`
|
|
10
|
+
<div class="container">
|
|
11
|
+
<svg
|
|
12
|
+
@wheel=${this.onWheel}
|
|
13
|
+
@mousedown=${this.onSvgMouseDown}
|
|
14
|
+
@dblclick=${this.onDoubleClick}
|
|
15
|
+
viewBox=${this.viewBox}
|
|
16
|
+
>
|
|
17
|
+
<image href=${this.imageUrl} @load=${this.onImageLoad}></image>
|
|
18
|
+
${this.shapes.map(shape => this.renderShape(shape))}
|
|
19
|
+
</svg>
|
|
20
|
+
</div>
|
|
21
|
+
`
|
|
22
|
+
}
|
|
23
|
+
}
|