ywana-core8 0.2.14 → 0.2.16

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ywana-core8",
3
- "version": "0.2.14",
3
+ "version": "0.2.16",
4
4
  "description": "ywana-core8",
5
5
  "homepage": "https://ywana.github.io/workspace",
6
6
  "author": "Ernesto Roldan Garcia",
package/src/html/index.js CHANGED
@@ -9,7 +9,7 @@ export { Header2 } from './header2'
9
9
  export { Icon } from './icon'
10
10
  export { List } from './list'
11
11
  export { MenuIcon, Menu, MenuItem, MenuSeparator } from './menu'
12
- export { CircularProgress, LinearProgress } from './progress'
12
+ export { CircularProgress, LinearProgress, StepProgress, RadialProgress, MultiProgress } from './progress'
13
13
  export { Property } from './property'
14
14
  export { RadioButton, RadioGroup } from './radio'
15
15
  export { Section } from './section'
@@ -1,5 +1,18 @@
1
- .image-viewer {
1
+ .image-viewer {
2
2
  display: flex;
3
3
  justify-content: center;
4
4
  align-items: center;
5
+ width: 100%;
6
+ height: 100%;
7
+ overflow: hidden;
8
+ }
9
+
10
+ .image-viewer canvas {
11
+ cursor: grab;
12
+ max-width: 100%;
13
+ max-height: 100%;
14
+ }
15
+
16
+ .image-viewer canvas:active {
17
+ cursor: grabbing;
5
18
  }
@@ -1,4 +1,4 @@
1
- import React, { useRef, useMemo, useEffect, useState } from "react";
1
+ import React, { useRef, useMemo, useEffect, useState, forwardRef, useImperativeHandle } from "react";
2
2
  import './ImageViewer.css'
3
3
 
4
4
  const ImageViewerTest = (props) => {
@@ -14,10 +14,10 @@ const SCROLL_SENSITIVITY = 0.0005;
14
14
  const MAX_ZOOM = 5;
15
15
  const MIN_ZOOM = 0.1;
16
16
 
17
- export const ImageViewer = ({ image }) => {
17
+ export const ImageViewer = forwardRef(({ image }, ref) => {
18
18
  const [offset, setOffset] = useState({ x: 0, y: 0 });
19
19
  const [zoom, setZoom] = useState(1);
20
- const [draggind, setDragging] = useState(false);
20
+ const [dragging, setDragging] = useState(false);
21
21
 
22
22
  const touch = useRef({ x: 0, y: 0 });
23
23
  const canvasRef = useRef(null);
@@ -27,9 +27,23 @@ export const ImageViewer = ({ image }) => {
27
27
 
28
28
  const clamp = (num, min, max) => Math.min(Math.max(num, min), max);
29
29
 
30
+ // Expose zoom functions to parent component
31
+ useImperativeHandle(ref, () => ({
32
+ zoomIn: () => {
33
+ setZoom(prevZoom => clamp(prevZoom + 0.2, MIN_ZOOM, MAX_ZOOM));
34
+ },
35
+ zoomOut: () => {
36
+ setZoom(prevZoom => clamp(prevZoom - 0.2, MIN_ZOOM, MAX_ZOOM));
37
+ },
38
+ resetZoom: () => {
39
+ setZoom(1);
40
+ setOffset({ x: 0, y: 0 });
41
+ }
42
+ }), []);
43
+
30
44
  const handleWheel = (event) => {
31
45
  const { deltaY } = event;
32
- if (!draggind) {
46
+ if (!dragging) {
33
47
  setZoom((zoom) =>
34
48
  clamp(zoom + deltaY * SCROLL_SENSITIVITY * -1, MIN_ZOOM, MAX_ZOOM)
35
49
  );
@@ -37,7 +51,7 @@ export const ImageViewer = ({ image }) => {
37
51
  };
38
52
 
39
53
  const handleMouseMove = (event) => {
40
- if (draggind) {
54
+ if (dragging) {
41
55
  const { x, y } = touch.current;
42
56
  const { clientX, clientY } = event;
43
57
  setOffset({
@@ -57,34 +71,50 @@ export const ImageViewer = ({ image }) => {
57
71
  const handleMouseUp = () => setDragging(false);
58
72
 
59
73
  const draw = () => {
60
- if (canvasRef.current) {
61
- const { width, height } = canvasRef.current;
62
- const context = canvasRef.current.getContext("2d");
74
+ if (!canvasRef.current || !background.complete || !background.naturalWidth) {
75
+ return;
76
+ }
77
+
78
+ try {
79
+ const canvas = canvasRef.current;
80
+ const { width, height } = canvas;
81
+ const context = canvas.getContext("2d");
63
82
 
64
83
  // Set canvas dimensions
65
- canvasRef.current.width = width;
66
- canvasRef.current.height = height;
84
+ canvas.width = width;
85
+ canvas.height = height;
67
86
 
68
- // Clear canvas and scale it
87
+ // Save context state
88
+ context.save();
89
+
90
+ // Clear canvas and apply transformations
69
91
  context.translate(-offset.x, -offset.y);
70
92
  context.scale(zoom, zoom);
71
93
  context.clearRect(0, 0, width, height);
72
94
 
73
95
  // Make sure we're zooming to the center
74
- const x = (context.canvas.width / zoom - background.width) / 2;
75
- const y = (context.canvas.height / zoom - background.height) / 2;
96
+ const x = (canvas.width / zoom - background.width) / 2;
97
+ const y = (canvas.height / zoom - background.height) / 2;
76
98
 
77
99
  // Draw image
78
100
  context.drawImage(background, x, y);
101
+
102
+ // Restore context state
103
+ context.restore();
104
+ } catch (error) {
105
+ console.warn('Error drawing image:', error);
79
106
  }
80
107
  };
81
108
 
82
109
  useEffect(() => {
110
+ const container = containerRef.current;
111
+ if (!container) return;
112
+
83
113
  observer.current = new ResizeObserver((entries) => {
84
114
  entries.forEach(({ target }) => {
85
115
  const { width, height } = background;
86
116
  // If width of the container is smaller than image, scale image down
87
- if (target.clientWidth < width) {
117
+ if (target.clientWidth < width && canvasRef.current) {
88
118
  // Calculate scale
89
119
  const scale = target.clientWidth / width;
90
120
 
@@ -97,16 +127,23 @@ export const ImageViewer = ({ image }) => {
97
127
  }
98
128
  });
99
129
  });
100
- observer.current.observe(containerRef.current);
101
130
 
102
- return () => observer.current.unobserve(containerRef.current);
131
+ observer.current.observe(container);
132
+
133
+ return () => {
134
+ if (observer.current && container) {
135
+ observer.current.unobserve(container);
136
+ }
137
+ };
103
138
  }, []);
104
139
 
105
140
  useEffect(() => {
141
+ if (!image) return;
142
+
106
143
  background.src = image;
107
144
 
108
- if (canvasRef.current) {
109
- background.onload = () => {
145
+ const handleImageLoad = () => {
146
+ if (canvasRef.current) {
110
147
  // Get the image dimensions
111
148
  const { width, height } = background;
112
149
  canvasRef.current.width = width;
@@ -114,13 +151,25 @@ export const ImageViewer = ({ image }) => {
114
151
 
115
152
  // Set image as background
116
153
  canvasRef.current.getContext("2d").drawImage(background, 0, 0);
117
- };
118
- }
119
- }, [background]);
154
+ }
155
+ };
156
+
157
+ const handleImageError = () => {
158
+ console.warn('Failed to load image:', image);
159
+ };
160
+
161
+ background.onload = handleImageLoad;
162
+ background.onerror = handleImageError;
163
+
164
+ return () => {
165
+ background.onload = null;
166
+ background.onerror = null;
167
+ };
168
+ }, [image, background]);
120
169
 
121
170
  useEffect(() => {
122
171
  draw();
123
- }, [zoom, offset]);
172
+ }, [zoom, offset, background.src]);
124
173
 
125
174
  return (
126
175
  <div className="image-viewer" ref={containerRef}>
@@ -133,7 +182,7 @@ export const ImageViewer = ({ image }) => {
133
182
  />
134
183
  </div>
135
184
  );
136
- };
185
+ });
137
186
 
138
187
 
139
188
  export default ImageViewer;
@@ -1,12 +1,16 @@
1
1
  .viewer {
2
+ position: fixed;
3
+ top: 0;
4
+ left: 0;
2
5
  width: 100vw;
3
6
  height: 100vh;
4
- background-color: rgba(0, 0, 0, .75);
7
+ background-color: rgba(0, 0, 0, .85);
5
8
  display: grid;
6
9
  grid-template-columns: 1fr auto;
7
10
  grid-template-rows: 4rem 1fr;
8
11
  grid-template-areas: "header aside" "main aside";
9
12
  color: #FFF;
13
+ z-index: 1000;
10
14
  }
11
15
 
12
16
  .viewer>header {
@@ -34,6 +38,7 @@
34
38
 
35
39
  .viewer>main>.resizer {
36
40
  width: 100%;
41
+ height: 100%;
37
42
  overflow: hidden;
38
43
  position: relative;
39
44
  display: flex;
@@ -42,9 +47,18 @@
42
47
  padding: 1rem;
43
48
  }
44
49
 
45
- .viewer>main>.resizer img {
50
+ .viewer>main>.resizer picture {
46
51
  width: 100%;
47
- object-fit: fill;
52
+ height: 100%;
53
+ display: flex;
54
+ justify-content: center;
55
+ align-items: center;
56
+ }
57
+
58
+ .viewer>main>.resizer img {
59
+ max-width: 100%;
60
+ max-height: 100%;
61
+ object-fit: contain;
48
62
  }
49
63
 
50
64
  @media (min-width: 800px) {
@@ -56,9 +70,12 @@
56
70
 
57
71
  .viewer>aside {
58
72
  min-width: 22rem;
73
+ max-width: 25rem;
59
74
  grid-area: aside;
60
75
  display: none;
61
- background-color: rgb(50, 50, 50);
76
+ background-color: rgba(40, 40, 40, 0.95);
77
+ backdrop-filter: blur(10px);
78
+ border-left: 1px solid rgba(255, 255, 255, 0.1);
62
79
  }
63
80
 
64
81
  .viewer>aside.open {
@@ -67,20 +84,38 @@
67
84
  flex-direction: column;
68
85
  }
69
86
 
87
+ .viewer>aside main {
88
+ flex: 1;
89
+ overflow-y: auto;
90
+ padding: 0;
91
+ }
92
+
70
93
  .viewer>aside .property>label {
71
94
  color: #AAA;
72
95
  }
73
96
 
74
97
  .viewer>footer {
75
- width: 15rem;
98
+ width: 12rem;
76
99
  position: absolute;
77
- bottom: 1rem;
78
- left: calc(50vw - 4rem);
79
- border-radius: 3px;
100
+ bottom: 2rem;
101
+ left: calc(50vw - 6rem);
102
+ border-radius: 8px;
80
103
  z-index: 100;
81
104
  display: flex;
82
105
  justify-content: space-between;
83
106
  align-items: center;
84
- padding: .5rem;
85
- background-color: rgba(0, 0, 0, .75);
107
+ padding: 0.75rem 1rem;
108
+ background-color: rgba(0, 0, 0, 0.8);
109
+ backdrop-filter: blur(10px);
110
+ border: 1px solid rgba(255, 255, 255, 0.1);
111
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
112
+ }
113
+
114
+ .viewer>footer .icon {
115
+ transition: all 0.2s ease;
116
+ }
117
+
118
+ .viewer>footer .icon:hover {
119
+ transform: scale(1.1);
120
+ color: #fff;
86
121
  }
@@ -3,35 +3,87 @@ import { Icon, Header, Text } from '../../html'
3
3
  import { ImageViewer} from '../image/ImageViewer'
4
4
  import './Viewer.css'
5
5
 
6
- const ViewerTest = (props) => {
6
+ export const ViewerTest = () => {
7
+ const [showViewer, setShowViewer] = useState(false);
8
+
9
+ const sampleInfo = (
10
+ <div style={{ padding: '1rem' }}>
11
+ <h3>Sample Image</h3>
12
+ <p><strong>Size:</strong> 1920x1080</p>
13
+ <p><strong>Format:</strong> PNG</p>
14
+ <p><strong>Created:</strong> 2024-01-15</p>
15
+ </div>
16
+ );
17
+
18
+ const sampleActions = [
19
+ <Icon key="download" icon="download" clickable title="Download" />,
20
+ <Icon key="share" icon="share" clickable title="Share" />
21
+ ];
22
+
7
23
  return (
8
- <Viewer src="https://pro.ywanadigital.com/files/2023/63bc36a840364cdd0b4c6670/63bc38f740364cdd0b4c6671/original/52905_TECNOLOG_A%20DICIEMBRE%20II%2043X199_Cart_n_Microcanal%20sencillo_43x199_es_A_1r5v-0.png" />
9
- )
10
- }
24
+ <div>
25
+ <button onClick={() => setShowViewer(true)}>
26
+ Open Viewer Test
27
+ </button>
28
+ {showViewer && (
29
+ <Viewer
30
+ title="Sample Image"
31
+ src="https://concepto.de/wp-content/uploads/2015/03/paisaje-800x409.jpg"
32
+ info={sampleInfo}
33
+ actions={sampleActions}
34
+ tools={true}
35
+ onClose={() => setShowViewer(false)}
36
+ />
37
+ )}
38
+ </div>
39
+ );
40
+ };
11
41
 
12
42
  /**
13
43
  * Viewer
14
44
  */
15
45
  export const Viewer = ({ title, src, info, actions = [], tools = false, onClose }) => {
16
-
17
- const [showDetails, setShowDetails] = useState(false)
46
+ const [showDetails, setShowDetails] = useState(false);
47
+ const [imageViewerRef, setImageViewerRef] = useState(null);
18
48
 
19
49
  function toggleDetails() {
20
- setShowDetails(!showDetails)
50
+ setShowDetails(!showDetails);
21
51
  }
22
52
 
23
- const headerTitle = <Text use="headline6">{title}</Text>
53
+ function handleZoomIn() {
54
+ if (imageViewerRef && imageViewerRef.zoomIn) {
55
+ imageViewerRef.zoomIn();
56
+ }
57
+ }
58
+
59
+ function handleZoomOut() {
60
+ if (imageViewerRef && imageViewerRef.zoomOut) {
61
+ imageViewerRef.zoomOut();
62
+ }
63
+ }
64
+
65
+ function handleZoomReset() {
66
+ if (imageViewerRef && imageViewerRef.resetZoom) {
67
+ imageViewerRef.resetZoom();
68
+ }
69
+ }
70
+
71
+ const headerTitle = <Text use="headline6">{title}</Text>;
72
+
24
73
  return (
25
74
  <div className="viewer">
26
- <Header icon="view" title={headerTitle} >
27
- {onClose ? <Icon icon="close" clickable action={onClose} /> : null}
28
- {showDetails ? '' : <Icon icon="info" clickable action={toggleDetails} />}
75
+ <Header icon="view" title={headerTitle}>
29
76
  {actions}
77
+ {!showDetails && <Icon icon="info" clickable action={toggleDetails} />}
78
+ {onClose && <Icon icon="close" clickable action={onClose} />}
30
79
  </Header>
31
80
  <main>
32
81
  <div className="resizer">
33
82
  <picture>
34
- <ImageViewer image={src} />
83
+ <ImageViewer
84
+ image={src}
85
+ ref={setImageViewerRef}
86
+ />
35
87
  </picture>
36
88
  </div>
37
89
  </main>
@@ -43,13 +95,13 @@ export const Viewer = ({ title, src, info, actions = [], tools = false, onClose
43
95
  {info}
44
96
  </main>
45
97
  </aside>
46
- {tools ? (
98
+ {tools && (
47
99
  <footer>
48
- <Icon clickable icon="zoom_out" />
49
- <Icon clickable icon="zoom_out_map" />
50
- <Icon clickable icon="zoom_in" />
100
+ <Icon clickable icon="zoom_out" action={handleZoomOut} />
101
+ <Icon clickable icon="zoom_out_map" action={handleZoomReset} />
102
+ <Icon clickable icon="zoom_in" action={handleZoomIn} />
51
103
  </footer>
52
- ) : null }
104
+ )}
53
105
  </div>
54
- )
55
- }
106
+ );
107
+ };