@c4h/chuci 0.1.0

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.
@@ -0,0 +1,190 @@
1
+ import { CcViewerBase } from './cc-viewer-base'
2
+ import Viewer from 'viewerjs'
3
+ import viewerStyles from 'viewerjs/dist/viewer.css?inline'
4
+
5
+ export class CcViewerImage extends CcViewerBase {
6
+ private viewer?: Viewer
7
+ private container?: HTMLDivElement
8
+ private imageUrl = ''
9
+
10
+ protected doOpen(url: string): void {
11
+ this.imageUrl = url
12
+
13
+ setTimeout(() => {
14
+ this.container = this.query('#imageContainer') ?? undefined
15
+ if (!this.container) return
16
+
17
+ const img = document.createElement('img')
18
+ img.src = this.imageUrl
19
+ img.style.display = 'none' // 元の画像を非表示に
20
+
21
+ this.container.appendChild(img)
22
+
23
+ // ViewerJSをインラインモードで使用して、独自のコンテナ内で表示
24
+ this.viewer = new Viewer(img, {
25
+ inline: true, // インラインモードに変更
26
+ container: this.container, // コンテナを指定
27
+ toolbar: {
28
+ zoomIn: true,
29
+ zoomOut: true,
30
+ oneToOne: true,
31
+ reset: true,
32
+ prev: false,
33
+ play: false,
34
+ next: false,
35
+ rotateLeft: true,
36
+ rotateRight: true,
37
+ flipHorizontal: true,
38
+ flipVertical: true,
39
+ },
40
+ navbar: false,
41
+ title: false,
42
+ keyboard: true,
43
+ backdrop: false, // インラインモードでは背景を無効化
44
+ button: false, // 閉じるボタンを無効化
45
+ movable: true,
46
+ zoomable: true,
47
+ rotatable: true,
48
+ scalable: true,
49
+ transition: true,
50
+ fullscreen: false,
51
+ ready: () => {
52
+ this.addNavigationListeners()
53
+ }
54
+ })
55
+
56
+ // show()を呼ばない - インラインモードでは自動的に表示される
57
+ }, 0)
58
+ }
59
+
60
+ protected doClose(): void {
61
+ if (this.viewer) {
62
+ this.viewer.destroy()
63
+ this.viewer = undefined
64
+ }
65
+ this.imageUrl = ''
66
+ }
67
+
68
+ protected getViewerContent(): string {
69
+ return `
70
+ <div id="imageContainer">
71
+ ${this.isLoading ? '<div class="loading">Loading...</div>' : ''}
72
+ </div>
73
+ `
74
+ }
75
+
76
+ protected getCustomStyles(): string {
77
+ const styles = this.css`
78
+ ${viewerStyles}
79
+
80
+ :host {
81
+ --cc-viewer-z-index-each: 1000;
82
+ }
83
+
84
+ #imageContainer {
85
+ position: fixed;
86
+ top: 0;
87
+ left: 0;
88
+ width: 100%;
89
+ height: 100%;
90
+ z-index: var(--cc-viewer-z-index-each, 1000);
91
+ background: rgba(0, 0, 0, 0.9);
92
+ display: flex;
93
+ align-items: center;
94
+ justify-content: center;
95
+ }
96
+
97
+ /* ViewerJS inline mode adjustments */
98
+ .viewer-container {
99
+ width: 100% !important;
100
+ height: 100% !important;
101
+ position: relative !important;
102
+ }
103
+
104
+ .viewer-canvas {
105
+ width: 100% !important;
106
+ height: 100% !important;
107
+ }
108
+
109
+ /* Hide the original image */
110
+ #imageContainer > img {
111
+ display: none !important;
112
+ }
113
+
114
+ /* Ensure viewer takes full space */
115
+ .viewer-container.viewer-inline {
116
+ width: 100% !important;
117
+ height: 100% !important;
118
+ }
119
+
120
+ ${this.getNavigationStyles()}
121
+
122
+ /* Override viewerjs styles for navigation */
123
+ :host ::ng-deep .viewer-container {
124
+ position: relative;
125
+ }
126
+ `
127
+
128
+ return styles
129
+ }
130
+
131
+ // Override to do setup after render
132
+ protected onAfterRender(): void {
133
+ if (!this.isShow || !this.imageUrl) return
134
+
135
+ // Avoid duplicate initialization
136
+ if (this.viewer) return
137
+
138
+ this.container = this.query('#imageContainer') as HTMLDivElement
139
+ if (!this.container) return
140
+
141
+ const img = document.createElement('img')
142
+ img.src = this.imageUrl
143
+ img.style.display = 'none' // 元の画像を非表示に
144
+
145
+ this.container.appendChild(img)
146
+
147
+ // ViewerJSをインラインモードで使用して、独自のコンテナ内で表示
148
+ this.viewer = new Viewer(img, {
149
+ inline: true, // インラインモードに変更
150
+ container: this.container, // コンテナを指定
151
+ toolbar: {
152
+ zoomIn: true,
153
+ zoomOut: true,
154
+ oneToOne: true,
155
+ reset: true,
156
+ prev: false,
157
+ play: false,
158
+ next: false,
159
+ rotateLeft: true,
160
+ rotateRight: true,
161
+ flipHorizontal: true,
162
+ flipVertical: true,
163
+ },
164
+ navbar: false,
165
+ title: false,
166
+ keyboard: true,
167
+ backdrop: false, // インラインモードでは背景を無効化
168
+ button: false, // 閉じるボタンを無効化
169
+ movable: true,
170
+ zoomable: true,
171
+ rotatable: true,
172
+ scalable: true,
173
+ transition: true,
174
+ fullscreen: false,
175
+ ready: () => {
176
+ this.isLoading = false
177
+ }
178
+ })
179
+ }
180
+ }
181
+
182
+ if (!customElements.get('cc-viewer-image')) {
183
+ customElements.define('cc-viewer-image', CcViewerImage)
184
+ }
185
+
186
+ declare global {
187
+ interface HTMLElementTagNameMap {
188
+ 'cc-viewer-image': CcViewerImage
189
+ }
190
+ }
@@ -0,0 +1,86 @@
1
+ import { CcViewerBase } from './cc-viewer-base'
2
+
3
+ export class CcViewerPanorama extends CcViewerBase {
4
+ private imgUrl = ''
5
+
6
+ static get observedAttributes() {
7
+ return ['show']
8
+ }
9
+
10
+ attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null) {
11
+ if (name === 'show') {
12
+ this.isShow = newValue === 'true'
13
+ }
14
+ super.attributeChangedCallback(name, oldValue, newValue)
15
+ }
16
+
17
+ protected doOpen(imgUrl: string): void {
18
+ this.imgUrl = imgUrl
19
+ }
20
+
21
+ protected doClose(): void {
22
+ const iframeEl = this.query('.iframe') as HTMLIFrameElement
23
+ if (iframeEl) {
24
+ iframeEl.srcdoc = ''
25
+ }
26
+ this.imgUrl = ''
27
+ }
28
+
29
+ protected getViewerContent(): string {
30
+ return `<iframe class="iframe"></iframe>`
31
+ }
32
+
33
+ protected getCustomStyles(): string {
34
+ return `
35
+ .iframe {
36
+ width: 100%;
37
+ height: 100%;
38
+ border: 0;
39
+ }
40
+ `
41
+ }
42
+
43
+ protected onAfterRender(): void {
44
+ if (this.imgUrl && this.isShow) {
45
+ const iframeEl = this.query('.iframe') as HTMLIFrameElement
46
+ if (iframeEl) {
47
+ const iframeHtml = `
48
+ <!DOCTYPE html>
49
+ <html>
50
+ <head>
51
+ <title>A-Frame Panorama</title>
52
+ <style>
53
+ html,body {
54
+ width:100%;
55
+ height:100vh;
56
+ overflow: hidden;
57
+ }
58
+ .a-enter-vr, .a-enter-ar {
59
+ display: none;
60
+ }
61
+ </style>
62
+ <script src="https://aframe.io/releases/1.4.0/aframe.min.js"></script>
63
+ </head>
64
+ <body>
65
+ <a-scene embedded xr-mode-ui="enabled: false; XRMode: false;">
66
+ <a-sky src="${this.imgUrl}" rotation="0 -90 0"></a-sky>
67
+ <a-entity camera look-controls="reverseMouseDrag: true"></a-entity>
68
+ </a-scene>
69
+ </body>
70
+ </html>
71
+ `
72
+ iframeEl.srcdoc = iframeHtml
73
+ }
74
+ }
75
+ }
76
+ }
77
+
78
+ if (!customElements.get('cc-viewer-panorama')) {
79
+ customElements.define('cc-viewer-panorama', CcViewerPanorama)
80
+ }
81
+
82
+ declare global {
83
+ interface HTMLElementTagNameMap {
84
+ 'cc-viewer-panorama': CcViewerPanorama
85
+ }
86
+ }
@@ -0,0 +1,56 @@
1
+ export const viewerStyles = `
2
+ :host {
3
+ display: block;
4
+ position: fixed;
5
+ top: 0;
6
+ left: 0;
7
+ width: 100%;
8
+ height: 100%;
9
+ z-index: var(--cc-viewer-z-index, 1000);
10
+ }
11
+
12
+ .viewer-container {
13
+ position: fixed;
14
+ top: 0;
15
+ left: 0;
16
+ width: 100%;
17
+ height: 100%;
18
+ background: rgba(0, 0, 0, 0.9);
19
+ display: flex;
20
+ align-items: center;
21
+ justify-content: center;
22
+ }
23
+
24
+ .viewer-content {
25
+ position: relative;
26
+ max-width: 90%;
27
+ max-height: 90%;
28
+ width: 100%;
29
+ height: 100%;
30
+ display: flex;
31
+ align-items: center;
32
+ justify-content: center;
33
+ }
34
+
35
+ .close-button {
36
+ position: absolute;
37
+ top: 20px;
38
+ right: 20px;
39
+ background: rgba(0, 0, 0, 0.5);
40
+ color: white;
41
+ border: none;
42
+ border-radius: 4px;
43
+ width: 48px;
44
+ height: 48px;
45
+ display: flex;
46
+ align-items: center;
47
+ justify-content: center;
48
+ cursor: pointer;
49
+ transition: background 0.3s;
50
+ z-index: 10;
51
+ }
52
+
53
+ .close-button:hover {
54
+ background: rgba(0, 0, 0, 0.7);
55
+ }
56
+ `
@@ -0,0 +1,110 @@
1
+ import { CcViewerBase } from './cc-viewer-base'
2
+
3
+ export class CcViewerVideo extends CcViewerBase {
4
+ private videoUrl = ''
5
+ private fitToContainer = false
6
+
7
+ static get observedAttributes() {
8
+ return ['show', 'fit-to-container']
9
+ }
10
+
11
+ attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null) {
12
+ if (name === 'show') {
13
+ this.isShow = newValue === 'true'
14
+ } else if (name === 'fit-to-container') {
15
+ this.fitToContainer = newValue === 'true'
16
+ }
17
+ super.attributeChangedCallback(name, oldValue, newValue)
18
+ }
19
+
20
+ protected doOpen(url: string): void {
21
+ this.videoUrl = url
22
+ }
23
+
24
+ protected doClose(): void {
25
+ // Pause video before closing
26
+ const video = this.query('video')
27
+ if (video && 'pause' in video) {
28
+ (video as HTMLVideoElement).pause()
29
+ }
30
+ this.videoUrl = ''
31
+ }
32
+
33
+ protected getViewerContent(): string {
34
+ return `
35
+ <div class="video-container">
36
+ ${this.videoUrl ? `
37
+ <video
38
+ src="${this.videoUrl}"
39
+ controls
40
+ controlsList="nodownload"
41
+ class="${this.fitToContainer ? 'fit-to-container' : ''}"
42
+ >
43
+ Your browser does not support the video tag.
44
+ </video>
45
+ ` : '<div class="video-error">No video URL provided</div>'}
46
+ </div>
47
+ `
48
+ }
49
+
50
+ protected getCustomStyles(): string {
51
+ return `
52
+ .video-container {
53
+ display: flex;
54
+ justify-content: center;
55
+ align-items: center;
56
+ width: 100%;
57
+ height: 100%;
58
+ background: #000;
59
+ }
60
+
61
+ video {
62
+ max-width: 100%;
63
+ max-height: 100%;
64
+ width: auto;
65
+ height: auto;
66
+ outline: none;
67
+ }
68
+
69
+ video.fit-to-container {
70
+ width: 100%;
71
+ height: 100%;
72
+ object-fit: contain;
73
+ }
74
+
75
+ .video-error {
76
+ color: #fff;
77
+ text-align: center;
78
+ padding: 20px;
79
+ }
80
+ `
81
+ }
82
+
83
+ protected onAfterRender(): void {
84
+ const video = this.query('video')
85
+ if (video) {
86
+ video.addEventListener('error', (e) => this.handleVideoError(e))
87
+ }
88
+ }
89
+
90
+ private handleVideoError(_e: Event) {
91
+ const container = this.query('.video-container')
92
+ if (container) {
93
+ container.innerHTML = `
94
+ <div class="video-error">
95
+ Failed to load video: ${this.videoUrl}
96
+ </div>
97
+ `
98
+ }
99
+ }
100
+ }
101
+
102
+ if (!customElements.get('cc-viewer-video')) {
103
+ customElements.define('cc-viewer-video', CcViewerVideo)
104
+ }
105
+
106
+ declare global {
107
+ interface HTMLElementTagNameMap {
108
+ 'cc-viewer-video': CcViewerVideo
109
+ }
110
+ }
@@ -0,0 +1,76 @@
1
+ import { CcViewerBase } from './cc-viewer-base'
2
+
3
+ export class CcViewerYoutube extends CcViewerBase {
4
+ private videoUrl = ''
5
+
6
+ static get observedAttributes() {
7
+ return ['show']
8
+ }
9
+
10
+ attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null) {
11
+ if (name === 'show') {
12
+ this.isShow = newValue === 'true'
13
+ }
14
+ super.attributeChangedCallback(name, oldValue, newValue)
15
+ }
16
+
17
+ protected doOpen(videoUrl: string): void {
18
+ // Convert YouTube URL to embed format
19
+ const videoId = this.extractYouTubeId(videoUrl)
20
+ if (videoId) {
21
+ this.videoUrl = `https://www.youtube.com/embed/${videoId}`
22
+ } else {
23
+ this.videoUrl = videoUrl
24
+ }
25
+ }
26
+
27
+ private extractYouTubeId(url: string): string | null {
28
+ // Handle various YouTube URL formats
29
+ const patterns = [
30
+ /(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([^&\n?#]+)/,
31
+ /youtube\.com\/watch\?.*v=([^&\n?#]+)/
32
+ ]
33
+
34
+ for (const pattern of patterns) {
35
+ const match = url.match(pattern)
36
+ if (match) {
37
+ return match[1]
38
+ }
39
+ }
40
+
41
+ return null
42
+ }
43
+
44
+ protected doClose(): void {
45
+ const iframeEl = this.query('.iframe') as HTMLIFrameElement
46
+ if (iframeEl) {
47
+ iframeEl.src = ''
48
+ }
49
+ this.videoUrl = ''
50
+ }
51
+
52
+ protected getViewerContent(): string {
53
+ return `<iframe class="iframe" src="${this.videoUrl}" allowfullscreen></iframe>`
54
+ }
55
+
56
+ protected getCustomStyles(): string {
57
+ return `
58
+ .iframe {
59
+ position: relative;
60
+ width: 100%;
61
+ height: 100%;
62
+ border: 0;
63
+ }
64
+ `
65
+ }
66
+ }
67
+
68
+ if (!customElements.get('cc-viewer-youtube')) {
69
+ customElements.define('cc-viewer-youtube', CcViewerYoutube)
70
+ }
71
+
72
+ declare global {
73
+ interface HTMLElementTagNameMap {
74
+ 'cc-viewer-youtube': CcViewerYoutube
75
+ }
76
+ }