@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.
Files changed (68) hide show
  1. package/.editorconfig +29 -0
  2. package/.storybook/main.js +3 -0
  3. package/.storybook/preview.js +52 -0
  4. package/.storybook/server.mjs +8 -0
  5. package/CHANGELOG.md +11 -0
  6. package/README.md +0 -0
  7. package/assets/images/no-image.png +0 -0
  8. package/demo/index.html +22 -0
  9. package/dist/src/base-image-marker.d.ts +24 -0
  10. package/dist/src/base-image-marker.js +137 -0
  11. package/dist/src/base-image-marker.js.map +1 -0
  12. package/dist/src/index.d.ts +0 -0
  13. package/dist/src/index.js +2 -0
  14. package/dist/src/index.js.map +1 -0
  15. package/dist/src/ox-image-marker-mode.d.ts +13 -0
  16. package/dist/src/ox-image-marker-mode.js +140 -0
  17. package/dist/src/ox-image-marker-mode.js.map +1 -0
  18. package/dist/src/ox-image-marker-view.d.ts +4 -0
  19. package/dist/src/ox-image-marker-view.js +26 -0
  20. package/dist/src/ox-image-marker-view.js.map +1 -0
  21. package/dist/src/ox-image-marker.d.ts +33 -0
  22. package/dist/src/ox-image-marker.js +583 -0
  23. package/dist/src/ox-image-marker.js.map +1 -0
  24. package/dist/src/types.d.ts +20 -0
  25. package/dist/src/types.js +2 -0
  26. package/dist/src/types.js.map +1 -0
  27. package/dist/stories/ox-image-marker-mode.stories.d.ts +16 -0
  28. package/dist/stories/ox-image-marker-mode.stories.js +31 -0
  29. package/dist/stories/ox-image-marker-mode.stories.js.map +1 -0
  30. package/dist/stories/ox-image-marker-view.stories.d.ts +41 -0
  31. package/dist/stories/ox-image-marker-view.stories.js +103 -0
  32. package/dist/stories/ox-image-marker-view.stories.js.map +1 -0
  33. package/dist/stories/ox-image-marker.stories.d.ts +33 -0
  34. package/dist/stories/ox-image-marker.stories.js +86 -0
  35. package/dist/stories/ox-image-marker.stories.js.map +1 -0
  36. package/dist/tsconfig.tsbuildinfo +1 -0
  37. package/package.json +97 -0
  38. package/src/base-image-marker.ts +144 -0
  39. package/src/index.ts +0 -0
  40. package/src/ox-image-marker-mode.ts +145 -0
  41. package/src/ox-image-marker-view.ts +23 -0
  42. package/src/ox-image-marker.ts +626 -0
  43. package/src/types.ts +20 -0
  44. package/stories/ox-image-marker-mode.stories.ts +41 -0
  45. package/stories/ox-image-marker-view.stories.ts +134 -0
  46. package/stories/ox-image-marker.stories.ts +102 -0
  47. package/themes/app-theme.css +138 -0
  48. package/themes/calendar-theme.css +59 -0
  49. package/themes/dark.css +51 -0
  50. package/themes/form-theme.css +70 -0
  51. package/themes/grist-theme.css +175 -0
  52. package/themes/layout-theme.css +94 -0
  53. package/themes/light.css +51 -0
  54. package/themes/material-theme.css +23 -0
  55. package/themes/md-typescale-styles.css +100 -0
  56. package/themes/oops-theme.css +22 -0
  57. package/themes/report-theme.css +47 -0
  58. package/themes/spacing.css +23 -0
  59. package/themes/state-color.css +6 -0
  60. package/themes/tooltip-theme.css +11 -0
  61. package/translations/en.json +1 -0
  62. package/translations/ja.json +1 -0
  63. package/translations/ko.json +1 -0
  64. package/translations/ms.json +1 -0
  65. package/translations/zh.json +1 -0
  66. package/tsconfig.json +25 -0
  67. package/web-dev-server.config.mjs +27 -0
  68. 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
+ }