@operato/image-marker 8.0.0-beta.0 β†’ 8.0.0-beta.2

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/CHANGELOG.md CHANGED
@@ -3,6 +3,24 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [8.0.0-beta.2](https://github.com/hatiolab/operato/compare/v8.0.0-beta.1...v8.0.0-beta.2) (2025-01-08)
7
+
8
+
9
+ ### :bug: Bug Fix
10
+
11
+ * typo .npmignore ([d9c0c8c](https://github.com/hatiolab/operato/commit/d9c0c8c79abc688c3c2cfb6c37fcb689483a5977))
12
+
13
+
14
+
15
+ ## [8.0.0-beta.1](https://github.com/hatiolab/operato/compare/v8.0.0-beta.0...v8.0.0-beta.1) (2025-01-08)
16
+
17
+
18
+ ### :bug: Bug Fix
19
+
20
+ * missing .npmignore ([be05985](https://github.com/hatiolab/operato/commit/be05985abfae4af53501f718dd52932099f7fbcb))
21
+
22
+
23
+
6
24
  ## [8.0.0-beta.0](https://github.com/hatiolab/operato/compare/v8.0.0-alpha.56...v8.0.0-beta.0) (2025-01-07)
7
25
 
8
26
  **Note:** Version bump only for package @operato/image-marker
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@operato/image-marker",
3
3
  "description": "Webcomponents for image-marker chart following open-wc recommendations",
4
4
  "author": "heartyoh@hatiolab.com",
5
- "version": "8.0.0-beta.0",
5
+ "version": "8.0.0-beta.2",
6
6
  "main": "dist/src/index.js",
7
7
  "module": "dist/src/index.js",
8
8
  "license": "MIT",
@@ -50,9 +50,9 @@
50
50
  }
51
51
  },
52
52
  "dependencies": {
53
- "@operato/i18n": "^8.0.0-beta.0",
54
- "@operato/styles": "^8.0.0-beta.0",
55
- "@operato/utils": "^8.0.0-beta.0",
53
+ "@operato/i18n": "^8.0.0-beta.2",
54
+ "@operato/styles": "^8.0.0-beta.2",
55
+ "@operato/utils": "^8.0.0-beta.2",
56
56
  "d3": "^7.8.5",
57
57
  "d3-time-format": "^4.1.0",
58
58
  "i18next": "^23.11.5",
@@ -94,5 +94,5 @@
94
94
  "prettier --write"
95
95
  ]
96
96
  },
97
- "gitHead": "c4e9cc245659d050a9ffd66542083a6daad4bcb9"
97
+ "gitHead": "ee1b5124995accb99272d3b5854f3df1d8746dda"
98
98
  }
package/.editorconfig DELETED
@@ -1,29 +0,0 @@
1
- # EditorConfig helps developers define and maintain consistent
2
- # coding styles between different editors and IDEs
3
- # editorconfig.org
4
-
5
- root = true
6
-
7
-
8
- [*]
9
-
10
- # Change these settings to your own preference
11
- indent_style = space
12
- indent_size = 2
13
-
14
- # We recommend you to keep these unchanged
15
- end_of_line = lf
16
- charset = utf-8
17
- trim_trailing_whitespace = true
18
- insert_final_newline = true
19
-
20
- [*.md]
21
- trim_trailing_whitespace = false
22
-
23
- [*.json]
24
- indent_size = 2
25
-
26
- [*.{html,js,md}]
27
- block_comment_start = /**
28
- block_comment = *
29
- block_comment_end = */
@@ -1,3 +0,0 @@
1
- module.exports = {
2
- stories: ['../dist/stories/**/*.stories.{js,md,mdx}']
3
- }
@@ -1,52 +0,0 @@
1
- import { i18next } from '@operato/i18n'
2
-
3
- export const globalTypes = {
4
- locale: {
5
- name: 'Locale',
6
- description: 'Internationalization locale',
7
- toolbar: {
8
- icon: 'globe',
9
- items: [
10
- { value: 'en', right: 'πŸ‡ΊπŸ‡Έ', title: 'English' },
11
- { value: 'ko', right: 'πŸ‡°πŸ‡·', title: 'ν•œκ΅­μ–΄' },
12
- { value: 'zh', right: 'πŸ‡¨πŸ‡³', title: 'δΈ­ζ–‡' },
13
- { value: 'ja', right: 'πŸ‡―πŸ‡΅', title: 'ζ—₯本θͺž' },
14
- { value: 'ms', right: 'πŸ‡²πŸ‡Ύ', title: 'Bahasa Melayu' }
15
- ],
16
- showName: true
17
- }
18
- },
19
- theme: {
20
- name: 'Theme',
21
- description: 'Global theme for components',
22
- toolbar: {
23
- icon: 'paintbrush',
24
- items: [
25
- { value: 'light', title: 'Light' },
26
- { value: 'dark', title: 'Dark' }
27
- ],
28
- showName: true
29
- }
30
- }
31
- }
32
-
33
- export const decorators = [
34
- (Story, context) => {
35
- const { locale, theme } = context.globals
36
-
37
- if (locale) {
38
- i18next.changeLanguage(locale)
39
- }
40
-
41
- // Set the theme class for the document
42
- if (theme === 'dark') {
43
- document.documentElement.classList.add('dark')
44
- document.documentElement.classList.remove('light')
45
- } else {
46
- document.documentElement.classList.add('light')
47
- document.documentElement.classList.remove('dark')
48
- }
49
-
50
- return Story()
51
- }
52
- ]
@@ -1,8 +0,0 @@
1
- import { storybookPlugin } from '@web/dev-server-storybook'
2
- import baseConfig from '../web-dev-server.config.mjs'
3
-
4
- export default /** @type {import('@web/dev-server').DevServerConfig} */ ({
5
- ...baseConfig,
6
- open: '/',
7
- plugins: [storybookPlugin({ type: 'web-components' }), ...baseConfig.plugins]
8
- })
package/demo/index.html DELETED
@@ -1,22 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en-GB">
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1" />
6
- <style>
7
- body {
8
- background: #fafafa;
9
- }
10
-
11
- a {
12
- display: block;
13
- }
14
- </style>
15
- <link href="/themes/app-theme.css" rel="stylesheet" />
16
- <link href="/themes/input-theme.css" rel="stylesheet" />
17
- <link href="https://fonts.googleapis.com/css?family=Material+Icons&display=block" rel="stylesheet" />
18
- </head>
19
- <body>
20
- <a href="./index-select.html">select</a>
21
- </body>
22
- </html>
@@ -1,268 +0,0 @@
1
- import { LitElement, css, html, svg, nothing, PropertyValues } from 'lit'
2
- import { property, state, query } from 'lit/decorators.js'
3
- import { debounce } from 'lodash-es'
4
-
5
- import {
6
- GestureEventDoubleTap,
7
- GestureEventDrag,
8
- GestureEventPinch,
9
- GestureHelper
10
- } from '@operato/utils/gesture-helper.js'
11
- import { ImageProvider, Shape } from './types'
12
- import { DefaultImageProvider } from './default-image-provider'
13
-
14
- export class BaseImageMarker extends LitElement {
15
- static styles = [
16
- css`
17
- :host {
18
- display: flex;
19
- }
20
-
21
- .container {
22
- flex: 1;
23
- position: relative;
24
- }
25
-
26
- svg {
27
- position: absolute;
28
- top: 0;
29
- left: 0;
30
- cursor: pointer;
31
- width: 100%;
32
- height: 100%;
33
- touch-action: none;
34
- }
35
-
36
- .link:hover {
37
- cursor: pointer;
38
- }
39
- `
40
- ]
41
-
42
- @property({ type: String }) imageUrl: string = ''
43
- @property({ type: Array }) shapes: Shape[] = []
44
-
45
- @state() protected currentImage?: string
46
- @state() protected viewBox = '0 0 800 600'
47
- @state() protected zoomLevel = 1
48
- @state() private imageProvider?: ImageProvider = new DefaultImageProvider()
49
-
50
- protected debouncedUpdateImage = debounce(this.updateImage.bind(this), 300)
51
- protected zoomFactor = 0.1
52
- protected zoomThreshold = 0.05
53
-
54
- protected initialViewBox: string | null = null
55
-
56
- @query('svg') protected svg!: SVGSVGElement
57
- @query('image') protected image!: SVGImageElement
58
-
59
- private gestureHelper: GestureHelper | null = null
60
-
61
- connectedCallback(): void {
62
- super.connectedCallback()
63
- requestAnimationFrame(() => {
64
- this.gestureHelper = new GestureHelper(this.svg)
65
- })
66
- }
67
-
68
- disconnectedCallback(): void {
69
- super.disconnectedCallback()
70
-
71
- this.gestureHelper?.dispose()
72
- this.gestureHelper = null
73
- }
74
-
75
- protected updated(changes: PropertyValues): void {
76
- if (changes.has('imageUrl') || changes.has('imageProvider')) {
77
- this.zoomLevel = 1
78
- this.initialViewBox = null
79
- this.updateImage()
80
- }
81
- }
82
-
83
- public setImageProvider(provider: ImageProvider) {
84
- this.imageProvider = provider
85
- }
86
-
87
- protected onImageLoad(event: Event) {
88
- const element = event.target as SVGImageElement
89
- const img = new Image()
90
-
91
- img.onload = () => {
92
- const width = img.naturalWidth
93
- const height = img.naturalHeight
94
-
95
- if (!this.initialViewBox) {
96
- this.initialViewBox = `0 0 ${width} ${height}`
97
- this.viewBox = this.initialViewBox
98
-
99
- element.setAttribute('width', width.toString())
100
- element.setAttribute('height', height.toString())
101
- element.setAttribute('preserveAspectRatio', 'xMidYMid meet')
102
- } else {
103
- const [initialX, initialY, initialWidth, initialHeight] = this.initialViewBox.split(' ').map(Number)
104
- const scale = initialWidth / width
105
-
106
- element.setAttribute('width', (width * scale).toString())
107
- element.setAttribute('height', (height * scale).toString())
108
- }
109
- }
110
-
111
- img.src = this.currentImage!
112
- }
113
-
114
- protected async updateImage() {
115
- if (!this.imageProvider) {
116
- throw new Error('ImageProvider is not set')
117
- }
118
-
119
- if (!this.imageUrl) {
120
- return
121
- }
122
-
123
- const options = {
124
- id: this.imageUrl, // 이미지 IDλŠ” imageUrl 속성을 μ‚¬μš©
125
- scale: this.zoomLevel, // scale은 zoomLevel 속성을 μ‚¬μš©
126
- offsetX: 0, // offsetXλŠ” 0으둜 μ„€μ •
127
- offsetY: 0, // offsetYλŠ” 0으둜 μ„€μ •
128
- currentImage: this.currentImage // κΈ°μ‘΄ 이미지 객체 전달
129
- }
130
-
131
- this.currentImage = await this.imageProvider.getImage(options)
132
- }
133
-
134
- protected onSvgWheel(event: WheelEvent) {
135
- event.preventDefault()
136
-
137
- const { offsetX, offsetY, deltaY } = event
138
- const scale = deltaY > 0 ? 1 - this.zoomFactor : 1 + this.zoomFactor
139
-
140
- this.zoom(scale, offsetX, offsetY)
141
- }
142
-
143
- protected onSvgPinch(e: GestureEventPinch) {
144
- e.stopPropagation()
145
-
146
- const { scale, centerX, centerY } = e.detail
147
-
148
- console.log('pinch', e.detail)
149
-
150
- this.zoom(scale, centerX, centerY)
151
- }
152
-
153
- protected onSvgDoubleTap(e: GestureEventDoubleTap) {
154
- e.stopPropagation()
155
-
156
- this.reset()
157
- }
158
-
159
- protected onSvgDrag(e: GestureEventDrag) {
160
- const { deltaX, deltaY } = e.detail
161
- console.log('drag', e.detail)
162
-
163
- const [vbX, vbY, vbWidth, vbHeight] = this.viewBox.split(' ').map(Number)
164
-
165
- const scaleX = vbWidth / this.svg.clientWidth
166
- const scaleY = vbHeight / this.svg.clientHeight
167
- const scale = Math.max(scaleX, scaleY)
168
-
169
- const newVbX = vbX - deltaX * scale
170
- const newVbY = vbY - deltaY * scale
171
-
172
- this.viewBox = `${newVbX} ${newVbY} ${vbWidth} ${vbHeight}`
173
-
174
- this.debouncedUpdateImage()
175
- }
176
-
177
- zoom(scale: number, offsetX: number, offsetY: number) {
178
- if (Math.abs(scale) > this.zoomThreshold) {
179
- this.zoomLevel *= scale
180
-
181
- const [vbX, vbY, vbWidth, vbHeight] = this.viewBox.split(' ').map(Number)
182
- const mouseX = vbX + (offsetX / this.svg.clientWidth) * vbWidth
183
- const mouseY = vbY + (offsetY / this.svg.clientHeight) * vbHeight
184
-
185
- const newVbWidth = vbWidth / scale
186
- const newVbHeight = vbHeight / scale
187
-
188
- const newVbX = mouseX - (offsetX / this.svg.clientWidth) * newVbWidth
189
- const newVbY = mouseY - (offsetY / this.svg.clientHeight) * newVbHeight
190
-
191
- this.viewBox = `${newVbX} ${newVbY} ${newVbWidth} ${newVbHeight}`
192
-
193
- this.debouncedUpdateImage()
194
- }
195
- }
196
-
197
- public reset() {
198
- if (!this.initialViewBox) {
199
- this.zoomLevel = 1
200
- return
201
- }
202
-
203
- this.viewBox = this.initialViewBox!
204
- this.zoomLevel = 1
205
-
206
- this.debouncedUpdateImage()
207
- }
208
-
209
- protected handleLinkClick(shape: Shape) {
210
- this.dispatchEvent(
211
- new CustomEvent('link-clicked', {
212
- detail: { id: shape.id, link: shape.link },
213
- bubbles: true,
214
- composed: true
215
- })
216
- )
217
- }
218
-
219
- protected renderShape(shape: Shape) {
220
- const commonAttrs = {
221
- stroke: 'red',
222
- 'stroke-width': 1,
223
- fill: 'transparent'
224
- }
225
-
226
- switch (shape.type) {
227
- case 'text':
228
- return svg`<text x="${shape.x}" y="${shape.y}" text-anchor="middle" alignment-baseline="middle" fill="black">${shape.text}</text>`
229
- case 'rectangle':
230
- 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>`
231
- case 'circle':
232
- return svg`<circle cx="${shape.cx}" cy="${shape.cy}" r="${shape.r}" stroke="${commonAttrs.stroke}" stroke-width="${commonAttrs['stroke-width']}" fill="${commonAttrs.fill}"></circle>`
233
- case 'line':
234
- return svg`<line x1="${shape.x1}" y1="${shape.y1}" x2="${shape.x2}" y2="${shape.y2}" stroke="${commonAttrs.stroke}" stroke-width="${commonAttrs['stroke-width']}"></line>`
235
- case 'arrow':
236
- return svg`
237
- <defs>
238
- <marker id="arrowhead-${shape.id}" markerWidth="10" markerHeight="7" refX="10" refY="3.5" orient="auto">
239
- <polygon points="0 0, 10 3.5, 0 7" fill="${commonAttrs.stroke}"></polygon>
240
- </marker>
241
- </defs>
242
- <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>`
243
- case 'link':
244
- return svg`
245
- <rect class="link" x="${shape.x}" y="${shape.y}" width="${shape.width}" height="${shape.height}"
246
- stroke="${commonAttrs.stroke}" stroke-width="0"
247
- fill="${commonAttrs.stroke}" fill-opacity="0.3" @click="${() => this.handleLinkClick(shape)}">
248
- </rect>`
249
- default:
250
- return nothing
251
- }
252
- }
253
-
254
- protected getPointFromEvent(event: MouseEvent | PointerEvent | GestureEventDrag) {
255
- if ('clientX' in event) {
256
- return {
257
- x: event.clientX,
258
- y: event.clientY
259
- }
260
- } else {
261
- const { clientX, clientY } = event.detail
262
- return {
263
- x: clientX,
264
- y: clientY
265
- }
266
- }
267
- }
268
- }
@@ -1,7 +0,0 @@
1
- import { ImageOptions, ImageProvider } from './types'
2
-
3
- export class DefaultImageProvider implements ImageProvider {
4
- async getImage(options: ImageOptions): Promise<string> {
5
- return options.id
6
- }
7
- }
package/src/index.ts DELETED
@@ -1,6 +0,0 @@
1
- export * from './types.js'
2
- export * from './base-image-marker.js'
3
-
4
- export * from './ox-image-marker.js'
5
- export * from './ox-image-marker-view.js'
6
- export * from './ox-image-marker-mode.js'
@@ -1,158 +0,0 @@
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 = 'edit'
42
- @property({ type: String }) currentShapeType?: ShapeType
43
-
44
- render() {
45
- return html`
46
- <div class="palette">
47
- <md-icon
48
- title="Edit or View"
49
- ?selected=${this.currentMode !== 'shape'}
50
- @click=${() => this.setMode(this.currentMode !== 'edit' ? 'edit' : 'view')}
51
- >${this.currentMode !== 'view' ? 'brush' : 'drag_pan'}</md-icon
52
- >
53
-
54
- <md-icon
55
- title="Text"
56
- ?selected=${this.currentMode === 'shape' && 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 === 'shape' && this.currentShapeType === 'rectangle'}
64
- @click=${() => this.setShape('rectangle')}
65
- >rectangle</md-icon
66
- >
67
-
68
- <md-icon
69
- title="Circle"
70
- ?selected=${this.currentMode === 'shape' && this.currentShapeType === 'circle'}
71
- @click=${() => this.setShape('circle')}
72
- >circle</md-icon
73
- >
74
-
75
- <md-icon
76
- title="Triangle"
77
- ?selected=${this.currentMode === 'shape' && 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 === 'shape' && 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 === 'shape' && 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="Fit size" @click=${() => this.onClickFitSize()}>recenter</md-icon>
99
- </div>
100
-
101
- <div class="palette">
102
- <md-icon title="Delete" @click=${() => this.onClickDelete()}>delete</md-icon>
103
- <md-icon title="Save" @click=${() => this.onClickSave()}>save</md-icon>
104
- </div>
105
- `
106
- }
107
-
108
- public setMode(mode: ImageMarkerMode) {
109
- this.currentMode = mode
110
-
111
- this.dispatchEvent(
112
- new CustomEvent('mode-changed', {
113
- detail: { mode: this.currentMode, shapeType: this.currentShapeType },
114
- bubbles: true,
115
- composed: true
116
- })
117
- )
118
- }
119
-
120
- protected setShape(shapeType: ShapeType) {
121
- this.currentMode = 'shape'
122
- this.currentShapeType = shapeType
123
- this.dispatchEvent(
124
- new CustomEvent('mode-changed', {
125
- detail: { mode: 'shape', shapeType: this.currentShapeType },
126
- bubbles: true,
127
- composed: true
128
- })
129
- )
130
- }
131
-
132
- private onClickDelete() {
133
- this.dispatchEvent(
134
- new CustomEvent('tap-delete', {
135
- bubbles: true,
136
- composed: true
137
- })
138
- )
139
- }
140
-
141
- private onClickSave() {
142
- this.dispatchEvent(
143
- new CustomEvent('tap-save', {
144
- bubbles: true,
145
- composed: true
146
- })
147
- )
148
- }
149
-
150
- private onClickFitSize() {
151
- this.dispatchEvent(
152
- new CustomEvent('tap-fit-size', {
153
- bubbles: true,
154
- composed: true
155
- })
156
- )
157
- }
158
- }
@@ -1,24 +0,0 @@
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.onSvgWheel}
13
- @doubletap=${this.onSvgDoubleTap}
14
- @pinch=${this.onSvgPinch}
15
- @drag=${this.onSvgDrag}
16
- viewBox=${this.viewBox}
17
- >
18
- <image href=${this.currentImage} @load=${this.onImageLoad}></image>
19
- ${this.shapes.map(shape => this.renderShape(shape))}
20
- </svg>
21
- </div>
22
- `
23
- }
24
- }