@iservice365/layer-common 1.4.2 → 1.5.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @iservice365/layer-common
2
2
 
3
+ ## 1.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 1a1f3e0: Virtual patrol initial release
8
+
3
9
  ## 1.4.2
4
10
 
5
11
  ### Patch Changes
@@ -0,0 +1,66 @@
1
+ <template>
2
+ <v-row no-gutters>
3
+ <v-col cols="12">
4
+ <v-card
5
+ width="100%"
6
+ variant="outlined"
7
+ border="thin"
8
+ rounded="lg"
9
+ :loading="false"
10
+ >
11
+ <v-toolbar density="compact" color="grey-lighten-4">
12
+ <template #prepend>
13
+ <v-btn fab icon density="comfortable">
14
+ <v-icon>mdi-refresh</v-icon>
15
+ </v-btn>
16
+ </template>
17
+ <template #append>
18
+ <v-row no-gutters justify="end" align="center">
19
+ <span class="mr-2 text-caption text-fontgray">
20
+ {{ pageRange }}
21
+ </span>
22
+ <local-pagination v-model="page" :length="pages" />
23
+ </v-row>
24
+ </template>
25
+ </v-toolbar>
26
+
27
+ <v-data-table
28
+ :headers="headers"
29
+ :items="items"
30
+ item-value="_id"
31
+ items-per-page="20"
32
+ fixed-header
33
+ hide-default-footer
34
+ hide-default-header
35
+ style="max-height: calc(100vh - (180px))"
36
+ >
37
+ <template #item.permissions="{ value }">
38
+ <span class="text-caption font-weight-bold text-capitalize">
39
+ permissions
40
+ </span>
41
+ <v-chip>{{ value.length }}</v-chip>
42
+ </template>
43
+
44
+ <template #item.nature="{ item }">
45
+ <span class="text-capitalize">
46
+ {{ item.nature }}
47
+ </span>
48
+ </template>
49
+ </v-data-table>
50
+ </v-card>
51
+ </v-col>
52
+ </v-row>
53
+ </template>
54
+
55
+ <script setup lang="ts">
56
+ const page = ref(1);
57
+ const pages = ref(0);
58
+ const pageRange = ref("-- - -- of --");
59
+
60
+ const items = ref<Array<Record<string, any>>>([]);
61
+
62
+ const headers = [
63
+ { title: "Name", value: "name" },
64
+ { title: "Nature", value: "nature" },
65
+ ];
66
+ </script>
@@ -0,0 +1,25 @@
1
+ <template>
2
+ <v-card flat border="md black">
3
+ <v-card-text class="d-flex justify-space-between align-center py-0">
4
+ <p>{{ label }}</p>
5
+ <v-switch v-model="toggle" :color="toggleColor" hide-details />
6
+ </v-card-text>
7
+ </v-card>
8
+ </template>
9
+
10
+ <script setup lang="ts">
11
+ defineProps({
12
+ label: {
13
+ type: String,
14
+ default: "Enable"
15
+ },
16
+ toggleColor: {
17
+ type: String,
18
+ default: 'primary'
19
+ }
20
+ })
21
+
22
+ const toggle = defineModel({default: false})
23
+ </script>
24
+
25
+ <style scoped></style>
@@ -13,7 +13,7 @@
13
13
  </template>
14
14
  </v-toolbar>
15
15
 
16
- <v-card-text style="max-height: 100vh; overflow-y: auto" class="pb-0">
16
+ <v-card-text style="max-height: 100vh; overflow-y: auto" class="pb-0" :class="contentNoPadding ? 'pa-0' : ''">
17
17
  <slot name="content" />
18
18
  </v-card-text>
19
19
 
@@ -90,6 +90,10 @@ const prop = defineProps({
90
90
  type: String,
91
91
  default: "Details",
92
92
  },
93
+ contentNoPadding: {
94
+ type: Boolean,
95
+ default: false,
96
+ },
93
97
  });
94
98
 
95
99
  const emit = defineEmits(["close", "edit", "delete"]);
@@ -0,0 +1,61 @@
1
+ <template>
2
+ <ClientOnly>
3
+ <v-row no-gutters class="ckeditor-container w-100">
4
+ <ckeditor v-model="data" :editor="ClassicEditor" :config="config" />
5
+ </v-row>
6
+ </ClientOnly>
7
+ </template>
8
+
9
+ <script setup>
10
+ import {
11
+ ClassicEditor,
12
+ Essentials,
13
+ Paragraph,
14
+ Bold,
15
+ Italic,
16
+ Heading,
17
+ List,
18
+ Link,
19
+ Alignment,
20
+ Font
21
+ } from 'ckeditor5'
22
+ import { Ckeditor } from '@ckeditor/ckeditor5-vue'
23
+ import 'ckeditor5/ckeditor5.css';
24
+
25
+ import 'ckeditor5/ckeditor5.css'
26
+
27
+ const data = defineModel({ required: true, default: "" })
28
+
29
+ const config = computed(() => ({
30
+ licenseKey: 'GPL',
31
+ plugins: [Essentials, Paragraph, Bold, Italic, Heading, List, Alignment, Font],
32
+ toolbar: [
33
+ 'undo', 'redo',
34
+ '|', 'bold', 'italic',
35
+ '|', 'fontSize', 'fontColor', 'fontBackgroundColor',
36
+ '|', 'bulletedList', 'numberedList',
37
+ '|', 'heading', '|', 'alignment:left', 'alignment:center', 'alignment:right', 'alignment:justify',
38
+ ]
39
+ }))
40
+ </script>
41
+
42
+ <style scoped>
43
+ .ckeditor-container {
44
+ width: 100%;
45
+ max-width: 100% !important;
46
+ overflow: hidden;
47
+ }
48
+
49
+ :deep(.ck.ck-editor) {
50
+ width: 100% !important;
51
+ max-width: 100%;
52
+ box-sizing: border-box;
53
+ }
54
+
55
+ :deep(.ck-editor__editable_inline) {
56
+ min-height: 200px;
57
+ width: 100% !important;
58
+ box-sizing: border-box;
59
+ }
60
+ </style>
61
+
@@ -0,0 +1,98 @@
1
+ <template>
2
+ <v-dialog v-model="overlay" v-if="overlay" width="100%" height="100%" opacity="50">
3
+ <v-row align="center" justify="center" class="fill-height" style="position: relative;">
4
+ <v-carousel hide-delimiters width="100%" height="100%" :show-arrows="images.length > 1 ? 'hover' : false"
5
+ v-model="activeIndex">
6
+ <template v-for="x, index in images" :key="x._id">
7
+ <v-carousel-item height="100%" width="100%" rounded="lg">
8
+ <v-row no-gutters class="w-100 h-100" align="center">
9
+ <v-img :lazy-src="getFileUrl(x)" :src="getFileUrl(x)" width="70%" height="70%" :alt="'Image Viewer Card -' + index"></v-img>
10
+ </v-row>
11
+ <template v-slot:placeholder>
12
+ <div class="d-flex align-center justify-center fill-height">
13
+ <v-progress-circular color="grey-lighten-4" indeterminate></v-progress-circular>
14
+ </div>
15
+ </template>
16
+ </v-carousel-item>
17
+ </template>
18
+
19
+ <template v-slot:prev="{ props }">
20
+ <v-btn color="white" variant="outlined" class="text-white" icon="mdi-chevron-left"
21
+ @click="props.onClick"></v-btn>
22
+ </template>
23
+ <template v-slot:next="{ props }">
24
+ <v-btn color="white" class="text-white" variant="outlined" icon="mdi-chevron-right"
25
+ @click="props.onClick"></v-btn>
26
+ </template>
27
+ </v-carousel>
28
+ <div style="position: absolute; top: 2%; left: 0%; right: 0%; z-index: 2"
29
+ class="cursor-pointer text-white text-h6 custom-shadow d-flex justify-space-between" @click="overlay = false">
30
+ <v-row no-gutters style="position: relative">
31
+ <v-col cols="8" xs="6" sm="2" md="2" lg="2">
32
+ <v-btn prepend-icon="mdi-close" text="Close" rounded="lg" color="secondary" class="ml-2"
33
+ style="position: absolute;"></v-btn>
34
+ </v-col>
35
+
36
+ </v-row>
37
+
38
+ </div>
39
+ <span class="text-white text-16px d-flex w-100 mt-2 justify-center" style="position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%);">{{ `${activeIndex + 1}/${images.length}`
40
+ }}</span>
41
+
42
+ </v-row>
43
+ </v-dialog>
44
+ </template>
45
+
46
+ <script setup lang="ts">
47
+ const props = defineProps({
48
+ activeImageId: {
49
+ type: String,
50
+ required: false,
51
+ },
52
+ isFavorite: {
53
+ type: Boolean,
54
+ default: false,
55
+ required: false
56
+ },
57
+ images: {
58
+ type: Array as PropType<string[]>,
59
+ default: []
60
+ }
61
+ });
62
+
63
+
64
+ const { getFileUrl } = useFile()
65
+ const overlay = defineModel({ required: true, default: false });
66
+ const activeIndex = ref(0);
67
+ const { currentUser} = useLocalAuth();
68
+
69
+ const emit = defineEmits(['share', 'like'])
70
+
71
+ watchEffect(() => {
72
+ if (props.activeImageId && props.images.length > 0) {
73
+ const index =
74
+ props.images?.findIndex((x) => x == props.activeImageId) || 0;
75
+ if (index !== -1) {
76
+ activeIndex.value = index;
77
+ } else activeIndex.value = 0;
78
+ } else {
79
+ return (activeIndex.value = 0);
80
+ }
81
+ });
82
+
83
+
84
+ const handleShare = () => {
85
+ overlay.value = false;
86
+ emit('share')
87
+ }
88
+
89
+ const handleLike = () => {
90
+ emit('like')
91
+ }
92
+ </script>
93
+
94
+ <style scoped>
95
+ .custom-shadow {
96
+ text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.8);
97
+ }
98
+ </style>
@@ -1,26 +1,52 @@
1
1
  <template>
2
- <v-row no-gutters class="w-100 pb-5" @click="resetErrorMessage">
3
- <v-file-upload v-model="uploadFiles" density="compact" @update:model-value="handleUpdateValue"
4
- :loading="processing" :disabled="processing" :height="height" :title="title" :accept="accept"
5
- name="upload_images" class="text-caption w-100" clearable :multiple="multiple">
6
- <template v-slot:item="{ props: itemProps, file }">
7
- <v-file-upload-item v-bind="itemProps" lines="one" nav>
8
- <template v-slot:prepend>
9
- <v-avatar size="32" rounded></v-avatar>
10
- </template>
11
-
12
- <template v-slot:clear="{ props: clearProps }">
13
- <v-btn color="primary" @click="handleRemove(file)"></v-btn>
14
- </template>
15
- </v-file-upload-item>
2
+ <v-row no-gutters class="w-100 pb-5" @click="resetErrorMessage">
3
+
4
+ <!-- VIEW MODE -->
5
+ <template v-if="viewMode">
6
+ <div class="w-100">
7
+ <v-file-upload-item v-for="({ file }, idx) in filesCollection" :key="fileKey(file)" :file="file" lines="one" nav
8
+ @click="handleClickItem(file)">
9
+ <template #prepend>
10
+ <v-avatar size="32" rounded></v-avatar>
11
+ </template>
12
+
13
+ <!-- delete hidden in view mode -->
14
+ <template #clear>
15
+ <!-- empty on purpose -->
16
+ </template>
17
+ </v-file-upload-item>
18
+ </div>
19
+ </template>
20
+
21
+ <!-- NORMAL MODE -->
22
+ <template v-else>
23
+ <v-file-upload v-model="uploadFiles" density="compact" @update:model-value="handleUpdateValue"
24
+ :loading="processing" :disabled="processing" :height="height" :title="title" :accept="accept"
25
+ :name="`upload_images`" class="text-caption w-100" clearable :multiple="multiple">
26
+ <template #item="{ props: itemProps, file }">
27
+ <v-file-upload-item v-bind="itemProps" lines="one" nav @click="handleClickItem(file)">
28
+ <template #prepend>
29
+ <v-avatar size="32" rounded></v-avatar>
16
30
  </template>
17
- </v-file-upload>
18
- <v-row no-gutters class="w-100" v-if="errorMessage">
31
+
32
+ <!-- delete button NOT shown in view mode -->
33
+ <template #clear="{ props: clearProps }">
34
+ <v-btn v-if="!viewMode" color="primary" @click.stop="handleRemove(file)"></v-btn>
35
+ </template>
36
+ </v-file-upload-item>
37
+ </template>
38
+ </v-file-upload>
39
+ </template>
40
+
41
+ <v-row no-gutters class="w-100" v-if="errorMessage">
19
42
  <p class="text-error w-100 text-center text-subtitle-2">{{ errorMessage }}</p>
20
43
  </v-row>
21
- </v-row>
44
+
45
+ <ImageCarousel v-model="showImageCarousel" :images="idsArray" :active-image-id="activeImageId" />
46
+ </v-row>
22
47
  </template>
23
48
 
49
+
24
50
  <script setup lang="ts">
25
51
  import { nextTick, ref, onMounted, watch } from 'vue'
26
52
 
@@ -44,11 +70,18 @@ const props = defineProps({
44
70
  accept: {
45
71
  type: String,
46
72
  default: "image/*"
73
+ },
74
+ viewMode: {
75
+ type: Boolean,
76
+ default: false
47
77
  }
48
78
  })
49
79
 
50
80
  const { addFile, deleteFile, getFileUrl, urlToFile } = useFile()
51
81
 
82
+ const showImageCarousel = ref(false)
83
+ const activeImageId = ref("")
84
+
52
85
  // The parent v-model binding
53
86
  const idsArray = defineModel<string[]>({ default: [] })
54
87
 
@@ -76,6 +109,21 @@ async function handleRemove(removedFile: File) {
76
109
 
77
110
  }
78
111
 
112
+ function handleClickItem(file: File) {
113
+ const isImage =
114
+ file.type.startsWith('image/') ||
115
+ /\.(jpg|jpeg|png|gif|webp|bmp|svg)$/i.test(file.name)
116
+
117
+ if (!isImage) return
118
+
119
+ const found = filesCollection.value.find((item) => fileKey(item.file) === fileKey(file))
120
+ if (!found) return
121
+
122
+ activeImageId.value = found.id
123
+ showImageCarousel.value = true
124
+ }
125
+
126
+
79
127
  async function handleUpdateValue(value: File[]) {
80
128
  await nextTick()
81
129
  const max = props.maxLength
@@ -95,12 +143,12 @@ async function handleUpdateValue(value: File[]) {
95
143
  for (const file of addedFiles) {
96
144
  const res = await addFile(file) // should return { id, url }
97
145
  if (res?.id) {
98
- filesCollection.value.push({ file, id: res.id })
146
+ filesCollection.value = [...filesCollection.value, { file, id: res.id }]
99
147
  }
100
148
  }
101
149
 
102
150
  uploadFiles.value = filesCollection.value.map((x) => x.file)
103
- idsArray.value = filesCollection.value.map((x) => x.id)
151
+
104
152
  } catch (err) {
105
153
  console.error('Upload failed', err)
106
154
  errorMessage.value = 'Failed to upload some files.'
@@ -132,33 +180,30 @@ function resetErrorMessage() {
132
180
 
133
181
 
134
182
  onMounted(async () => {
135
- if (idsArray.value.length > 0) {
136
- processing.value = true
137
- const loaded = await loadFilesFromIds(idsArray.value)
138
- filesCollection.value = loaded
139
- uploadFiles.value = loaded.map((x) => x.file)
140
- processing.value = false
141
- }
183
+ setTimeout(async () => {
184
+ if (idsArray.value.length > 0) {
185
+ const loadedFiles = await loadFilesFromIds(idsArray.value)
186
+ filesCollection.value = loadedFiles
187
+ uploadFiles.value = loadedFiles.map((x) => x.file)
188
+ }
189
+ }, 1000)
142
190
  })
143
191
 
192
+ watch(filesCollection, () => {
193
+ idsArray.value = [...filesCollection.value.map(x => x.id)]
194
+ }, { deep: true })
195
+
144
196
 
145
- watch(
146
- filesCollection,
147
- (newVal) => {
148
- idsArray.value = newVal.map((x) => x.id)
149
- },
150
- { deep: true }
151
- )
152
197
  </script>
153
198
 
154
199
 
155
200
  <style scoped>
156
201
  * :deep(.v-file-upload-title) {
157
- font-size: 1rem;
158
- font-weight: 500;
202
+ font-size: 1rem;
203
+ font-weight: 500;
159
204
  }
160
205
 
161
206
  * :deep(.v-file-upload-items) {
162
- min-width: 100%;
207
+ min-width: 100%;
163
208
  }
164
209
  </style>