@live-change/image-frontend 0.0.3

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,132 @@
1
+ import { ref, onUnmounted, watch } from 'vue'
2
+ import { api as useApi } from '@live-change/vue3-ssr'
3
+ import { Upload } from '@live-change/upload-frontend'
4
+ import { blobToDataUrl } from "./imageUtils.js"
5
+ import preProcessImageFile from "./preprocessImageFile.js";
6
+
7
+ const sleep = ms => new Promise(r => setTimeout(r, ms))
8
+
9
+ class ImageUpload {
10
+ constructor(purpose, uploadable, options = {}) {
11
+ const blob = uploadable.file
12
+ const {
13
+ fileName = blob?.name,
14
+ api = useApi(options.appContext),
15
+ uploadOptions = {}
16
+ } = options
17
+ this.options = options
18
+ this.purpose = purpose
19
+ this.fileName = fileName
20
+ this.uploadOptions = { appContext: options.appContext, api, ...uploadOptions }
21
+ this.api = api
22
+ this.id = options.generateId ? api.uid() : options.id
23
+ this.localUid = api.uid()
24
+ this.blob = null
25
+ this.uploadable = uploadable
26
+ this.size = null
27
+ const clientConfig = api.getServiceDefinition('image')?.clientConfig
28
+ this.config = clientConfig.upload
29
+
30
+ this.ownerType = options.ownerType
31
+ this.owner = options.owner
32
+ if(!this.ownerType || !this.owner) {
33
+ const client = api.client.value
34
+ if(client.user) {
35
+ this.ownerType = 'user_User'
36
+ this.owner = client.user
37
+ } else {
38
+ this.ownerType = 'session_Session'
39
+ this.owner = client.session
40
+ }
41
+ }
42
+
43
+ this.url = ref()
44
+ this.canvas = ref()
45
+
46
+ this.error = ref()
47
+ this.state = ref('starting')
48
+
49
+ this.prepared = false
50
+
51
+ this.preparePromise = null
52
+ this.uploadPromise = null
53
+ }
54
+
55
+ prepare() {
56
+ if(!this.preparePromise) this.preparePromise = (async () => {
57
+ if(this.options.unpreparedPreview) {
58
+ if(this.uploadable.canvas) {
59
+ if(this.options.saveCanvas) this.canvas.value = this.uploadable.canvas
60
+ this.url.value = this.uploadable.canvas.toDataURL(this.options.fileType || 'image/png')
61
+ } else if(this.uploadable.file) {
62
+ this.url.value = await blobToDataUrl(this.uploadable.file)
63
+ }
64
+ }
65
+
66
+ const processed = await preProcessImageFile(this.uploadable, { ...this.config, ...this.options })
67
+ this.blob = processed.blob
68
+ this.size = processed.size
69
+
70
+ if(this.options.preparedPreview) {
71
+ if(processed.canvas) {
72
+ if(this.options.saveCanvas) this.canvas.value = processed.canvas
73
+ this.url.value = processed.canvas.toDataURL(this.options.fileType || 'image/png')
74
+ } else {
75
+ this.url.value = await blobToDataUrl(this.blob)
76
+ }
77
+ //console.log("PREPARED PREVIEW!", processed, '=>', this.url.value)
78
+ }
79
+ const extension = this.blob.type.split('/')[1]
80
+ if(!this.fileName) {
81
+ this.fileName = 'unknown.' + extension
82
+ } else {
83
+ const nameSplit = this.fileName.split('.')
84
+ const currentExtension = nameSplit[nameSplit.length - 1]
85
+ if(currentExtension != extension) {
86
+ this.fileName = nameSplit.slice(0, -1).concat([extension]).join('.')
87
+ }
88
+ }
89
+ this.prepared = true
90
+ })()
91
+ return this.preparePromise
92
+ }
93
+
94
+ async upload() {
95
+ if(!this.uploadPromise) this.uploadPromise = (async () => {
96
+ await this.prepare()
97
+ await sleep(2000)
98
+
99
+ this.state.value = 'uploading'
100
+
101
+ this.fileUpload = new Upload(this.purpose, this.blob, { fileName: this.fileName, ...this.uploadOptions })
102
+ await this.fileUpload.promise
103
+
104
+ try {
105
+ const imageParams = {
106
+ image: this.id,
107
+ name: this.uploadable.name || this.uploadable.file?.name || this.options.name || 'unknown',
108
+ width: this.size.width,
109
+ height: this.size.height,
110
+ upload: this.fileUpload.id,
111
+ purpose: this.purpose,
112
+ ownerType: this.ownerType,
113
+ owner: this.owner,
114
+ crop: this.options.crop
115
+ }
116
+ console.log("CREATINNG IMAGE!", imageParams)
117
+ this.id = await this.api.command(["image", "createImage"], imageParams)
118
+ //this.url.value = `/api/image/image/${this.id}` - useless reload...
119
+ this.state.value = 'done'
120
+ } catch(error) {
121
+ this.error.value = error
122
+ }
123
+
124
+ this.state.value = 'done'
125
+
126
+ /// TODO: set this.url.value to server-side address
127
+ })()
128
+ return this.uploadPromise
129
+ }
130
+ }
131
+
132
+ export default ImageUpload
@@ -0,0 +1,105 @@
1
+ <template>
2
+ <div class="surface-overlay py-3 px-6 shadow-2 flex align-items-center justify-content-between
3
+ relative md:sticky top-0 z-5"
4
+ style="min-height: 80px" key="navbar">
5
+ <img src="/images/logo.svg" alt="Image" height="40" class="mr-0 lg:mr-6">
6
+ <a v-ripple class="cursor-pointer block lg:hidden text-700 p-ripple"
7
+ v-styleclass="{ selector: '@next', enterClass: 'hidden', leaveToClass: 'hidden', hideOnOutsideClick: true }">
8
+ <i class="pi pi-bars text-4xl"></i>
9
+ </a>
10
+ <div class="align-items-center flex-grow-1 justify-content-between hidden lg:flex absolute lg:static w-full surface-overlay left-0 top-100 z-1 shadow-2 lg:shadow-none">
11
+ <ul class="list-none p-0 m-0 flex lg:align-items-center select-none flex-column lg:flex-row">
12
+ <li>
13
+ <a v-ripple class="flex px-6 p-3 lg:px-3 lg:py-2 align-items-center text-600 hover:text-900 hover:surface-100 font-medium border-round cursor-pointer transition-colors transition-duration-150 p-ripple">
14
+ <i class="pi pi-home mr-2"></i>
15
+ <span>Home</span>
16
+ </a>
17
+ </li>
18
+ <li class="lg:relative">
19
+ <a v-ripple class="flex px-6 p-3 lg:px-3 lg:py-2 align-items-center text-600 hover:text-900 hover:surface-100 font-medium border-round cursor-pointer transition-colors transition-duration-150 p-ripple"
20
+ v-styleclass="{ selector: '@next', enterClass: 'hidden', enterActiveClass: 'scalein', leaveToClass: 'hidden', leaveActiveClass: 'fadeout', hideOnOutsideClick: true }">
21
+ <i class="pi pi-users mr-2"></i>
22
+ <span>Customers</span>
23
+ <i class="pi pi-angle-down ml-auto lg:ml-3"></i>
24
+ </a>
25
+ <ul class="list-none py-3 px-6 m-0 lg:px-0 lg:py-0 border-round shadow-0 lg:shadow-2 lg:border-1 border-50 lg:absolute surface-overlay hidden origin-top w-full lg:w-15rem cursor-pointer">
26
+ <li>
27
+ <a v-ripple class="flex p-3 align-items-center text-600 hover:text-900 hover:surface-100 transition-colors transition-duration-150 p-ripple">
28
+ <i class="pi pi-user-plus mr-2"></i>
29
+ <span class="font-medium">Add New</span>
30
+ </a>
31
+ </li>
32
+ <li class="relative">
33
+ <a v-ripple class="flex p-3 align-items-center text-600 hover:text-900 hover:surface-100 transition-colors transition-duration-150 p-ripple"
34
+ v-styleclass="{ selector: '@next', enterClass: 'hidden', enterActiveClass: 'scalein', leaveToClass: 'hidden', leaveActiveClass: 'fadeout', hideOnOutsideClick: true }">
35
+ <i class="pi pi-search mr-2"></i>
36
+ <span class="font-medium">Search</span>
37
+ <i class="pi pi-angle-down ml-auto lg:-rotate-90"></i>
38
+ </a>
39
+ <ul class="list-none py-3 pl-3 m-0 lg:px-0 lg:py-0 border-round shadow-0 lg:shadow-2 lg:border-1 border-50 lg:absolute surface-overlay hidden origin-top w-full lg:w-15rem cursor-pointer left-100 top-0">
40
+ <li>
41
+ <a v-ripple class="flex p-3 align-items-center text-600 hover:text-900 hover:surface-100 transition-colors transition-duration-150 p-ripple">
42
+ <i class="pi pi-shopping-cart mr-2"></i>
43
+ <span class="font-medium">Purchases</span>
44
+ </a>
45
+ </li>
46
+ <li class="relative">
47
+ <a v-ripple class="flex p-3 align-items-center text-600 hover:text-900 hover:surface-100 transition-colors transition-duration-150 p-ripple">
48
+ <i class="pi pi-comments mr-2"></i>
49
+ <span class="font-medium">Messages</span>
50
+ </a>
51
+ </li>
52
+ </ul>
53
+ </li>
54
+ </ul>
55
+ </li>
56
+ <li>
57
+ <a v-ripple class="flex px-6 p-3 lg:px-3 lg:py-2 align-items-center text-600 hover:text-900 hover:surface-100 font-medium border-round cursor-pointer transition-colors transition-duration-150 p-ripple">
58
+ <i class="pi pi-calendar mr-2"></i>
59
+ <span>Calendar</span>
60
+ </a>
61
+ </li>
62
+ <li>
63
+ <a v-ripple class="flex px-6 p-3 lg:px-3 lg:py-2 align-items-center text-600 hover:text-900 hover:surface-100 font-medium border-round cursor-pointer transition-colors transition-duration-150 p-ripple">
64
+ <i class="pi pi-chart-line mr-2"></i>
65
+ <span>Stats</span>
66
+ </a>
67
+ </li>
68
+ </ul>
69
+ <ul class="list-none p-0 m-0 flex lg:align-items-center select-none flex-column lg:flex-row border-top-1 surface-border lg:border-top-none">
70
+ <li>
71
+ <a v-ripple class="flex px-6 p-3 lg:px-3 lg:py-2 align-items-center text-600 hover:text-900 hover:surface-100 font-medium border-round cursor-pointer transition-colors transition-duration-150 p-ripple">
72
+ <i class="pi pi-inbox text-base lg:text-2xl mr-2 lg:mr-0"></i>
73
+ <span class="block lg:hidden font-medium">Inbox</span>
74
+ </a>
75
+ </li>
76
+ <li>
77
+ <a v-ripple class="flex px-6 p-3 lg:px-3 lg:py-3 align-items-center text-600 hover:text-900 hover:surface-100
78
+ font-medium border-round cursor-pointer transition-colors transition-duration-150 p-ripple">
79
+ <i class="pi pi-bell text-base lg:text-2xl mr-2 lg:mr-0 p-overlay-badge">
80
+ <Badge value="2"></Badge>
81
+ </i>
82
+ <span class="block lg:hidden font-medium">Notifications</span>
83
+ </a>
84
+ </li>
85
+ <li class="border-top-1 surface-border lg:border-top-none">
86
+ <a v-ripple class="flex px-6 p-3 lg:px-3 lg:py-2 align-items-center hover:surface-100 font-medium border-round cursor-pointer transition-colors transition-duration-150 p-ripple">
87
+ <img src="/images/empty-user-photo.svg" class="mr-3 lg:mr-0 border-circle" style="width: 28px; height: 28px"/>
88
+ <div class="block lg:hidden">
89
+ <div class="text-900 font-medium">Josephine Lillard</div>
90
+ <span class="text-600 font-medium text-sm">Marketing Specialist</span>
91
+ </div>
92
+ </a>
93
+ </li>
94
+ </ul>
95
+ </div>
96
+ </div>
97
+ </template>
98
+
99
+ <script setup>
100
+ import Badge from "primevue/badge"
101
+ </script>
102
+
103
+ <style scoped>
104
+
105
+ </style>
@@ -0,0 +1,57 @@
1
+ <template>
2
+ <div class="w-full sm:w-9 md:w-8 lg:w-6 surface-card p-4 shadow-2 border-round">
3
+ <div class="text-center mb-5">
4
+ <div class="text-900 text-3xl font-medium mb-3">
5
+ Upload Test
6
+ </div>
7
+ </div>
8
+
9
+ <div></div>
10
+ <!-- <pre>{{ JSON.stringify(upload, null, " ") }}</pre>-->
11
+
12
+ <FileInput @input="handleUpload" class="inline-block">
13
+ <Button label="Upload File" icon="pi pi-upload"></Button>
14
+ </FileInput>
15
+
16
+ <div v-if="image">
17
+ <h2>Image: {{ image }}</h2>
18
+ <Image :image="image" width="200" height="200" />
19
+ </div>
20
+ <!-- <UploadView v-if="upload" :upload="upload" cancelable class="surface-card shadow-2 border-round p-4 mt-3" />-->
21
+ </div>
22
+ </template>
23
+
24
+ <script setup>
25
+
26
+ import Button from "primevue/button"
27
+ import { ref, shallowRef, getCurrentInstance } from 'vue'
28
+ import { Upload, UploadView, FileInput } from "@live-change/upload-frontend"
29
+ import { uploadImage } from "./imageUploads.js"
30
+ import Image from "./Image.vue"
31
+
32
+ const upload = shallowRef()
33
+ const appContext = getCurrentInstance().appContext
34
+
35
+ const image = ref()
36
+
37
+ async function handleUpload(file) {
38
+ console.log("FILE", file)
39
+ upload.value = uploadImage('test', { file },
40
+ { preparedPreview: true, appContext, generateId : true, noStart: true })
41
+ image.value = upload.value.id
42
+ console.log("START PREPARE!")
43
+ await upload.value.prepare()
44
+ console.log("START UPLOAD!")
45
+ await upload.value.upload()
46
+ image.value = upload.value.id
47
+
48
+ console.log("UPLOADED", upload.value)
49
+
50
+ //upload.value = null
51
+ }
52
+
53
+ </script>
54
+
55
+ <style scoped>
56
+
57
+ </style>
@@ -0,0 +1,82 @@
1
+ function getElementPositionInWindow(element) {
2
+ let o = element
3
+ let x = 0
4
+ let y = 0
5
+ while (true) {
6
+ //console.log("OE", o, x, y)
7
+ x += o.offsetLeft
8
+ y += o.offsetTop
9
+ if(window.getComputedStyle(o).position == 'fixed') {
10
+ return { x, y }
11
+ }
12
+ o = o.offsetParent
13
+ if(!o) {
14
+ return {
15
+ x: x - document.documentElement.scrollLeft || 0,
16
+ y: y - document.documentElement.scrollTop || 0
17
+ }
18
+ }
19
+ if(o.scrollLeft) x -= o.scrollLeft
20
+ if(o.scrollTop) y -= o.scrollTop
21
+ }
22
+ }
23
+
24
+ function getElementPositionInDocument(element) {
25
+ let o = element
26
+ let x = 0
27
+ let y = 0
28
+ while (true) {
29
+ x += o.offsetLeft
30
+ y += o.offsetTop
31
+ if(window.getComputedStyle(o).position == 'fixed') {
32
+ return { x, y }
33
+ }
34
+ o = o.offsetParent
35
+ if(!o) {
36
+ return {
37
+ x: x || 0,
38
+ y: y || 0
39
+ }
40
+ }
41
+ if(o.scrollLeft) x -= o.scrollLeft
42
+ if(o.scrollTop) y -= o.scrollTop
43
+ }
44
+ }
45
+
46
+ function getElementPositionInElement(element, parent) {
47
+ let o = element
48
+ let x = 0
49
+ let y = 0
50
+ while (true) {
51
+ x += o.offsetLeft
52
+ y += o.offsetTop
53
+ if(window.getComputedStyle(o).position == 'fixed') {
54
+ return { x, y }
55
+ }
56
+ o = o.offsetParent
57
+ if(!o || o == parent) {
58
+ return {
59
+ x: x || 0,
60
+ y: y || 0
61
+ }
62
+ }
63
+ if(o.scrollLeft) x -= o.scrollLeft
64
+ if(o.scrollTop) y -= o.scrollTop
65
+ }
66
+ }
67
+
68
+ function getScrollParent(element, includeHidden) {
69
+ let style = getComputedStyle(element)
70
+ const excludeStaticParent = style.position === "absolute"
71
+ const overflowRegex = includeHidden ? /(auto|scroll|hidden)/ : /(auto|scroll)/
72
+ if (style.position === "fixed") return document.body
73
+ for (let parent = element; (parent = parent.parentElement);) {
74
+ style = getComputedStyle(parent)
75
+ if (excludeStaticParent && style.position === "static") continue
76
+ if (overflowRegex.test(style.overflow + style.overflowY + style.overflowX)) return parent
77
+ }
78
+ return document.body
79
+ }
80
+
81
+
82
+ export { getElementPositionInWindow, getElementPositionInDocument, getElementPositionInElement, getScrollParent }
@@ -0,0 +1,6 @@
1
+ import { clientEntry } from '@live-change/frontend-base/client-entry.js'
2
+ import App from './App.vue'
3
+ import { createRouter } from './router'
4
+
5
+
6
+ clientEntry(App, createRouter)
@@ -0,0 +1,6 @@
1
+ import { serverEntry } from '@live-change/frontend-base/server-entry.js'
2
+ import App from './App.vue'
3
+ import { createRouter } from './router'
4
+
5
+ const render = serverEntry(App, createRouter)
6
+ export { render }
@@ -0,0 +1,5 @@
1
+ import pica from 'pica'
2
+
3
+ const imageResizer = pica()
4
+
5
+ export default imageResizer
@@ -0,0 +1,29 @@
1
+ import { ref } from 'vue'
2
+ import ImageUpload from "./ImageUpload.js";
3
+
4
+ export const imageUploads = ref([])
5
+
6
+ async function handleUpload(upload, options) {
7
+ await upload.upload()
8
+ const existingIndex = imageUploads.value.findIndex(u => u.localUid === upload.localUid)
9
+ if(existingIndex !== -1) {
10
+ imageUploads.value.splice(existingIndex, 1)
11
+ }
12
+ console.log("IMAGE UPLOADS", imageUploads)
13
+ }
14
+
15
+ export function uploadImage(purpose, uploadable, options = {}) {
16
+ const upload = new ImageUpload(purpose, uploadable, options)
17
+ if(options.id) {
18
+ const existingIndex = imageUploads.value.findIndex(u => u.id == options.id)
19
+ if( existingIndex != -1 ) {
20
+ imageUploads.value[existingIndex].cancel()
21
+ imageUploads.value[existingIndex] = upload
22
+ handleUpload(upload, options)
23
+ return upload
24
+ }
25
+ }
26
+ imageUploads.value.push(upload)
27
+ handleUpload(upload, options)
28
+ return upload
29
+ }