@edgedev/create-edge-app 0.0.1
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/.eslintrc +10 -0
- package/.vscode/settings.json +14 -0
- package/README.md +63 -0
- package/app.vue +106 -0
- package/bin/cli-edge-app.js +99 -0
- package/components/.gitkeep +0 -0
- package/components/account.vue +84 -0
- package/components/bottomMenu.vue +35 -0
- package/components/dashboard.vue +186 -0
- package/components/editor.vue +250 -0
- package/components/formSubtypes/.gitkeep +0 -0
- package/components/topMenu.vue +17 -0
- package/components/userMenu.vue +64 -0
- package/composables/global.ts +15 -0
- package/composables/vuetify.ts +10 -0
- package/deploy.sh +6 -0
- package/emulator.sh +17 -0
- package/firebase.json +56 -0
- package/firestore.indexes.json +4 -0
- package/firestore.rules +294 -0
- package/firestore.rules.backup +1 -0
- package/functions/.runtimeconfig.json +5 -0
- package/functions/index.js +226 -0
- package/functions/index.js.backup +4 -0
- package/functions/package-lock.json +4535 -0
- package/functions/package.json +25 -0
- package/middleware/auth.ts +17 -0
- package/nuxt.config.ts +27 -0
- package/package.json +34 -0
- package/pages/app/[[page]]/[[collection]]/[[docId]].vue +48 -0
- package/pages/app/login.vue +16 -0
- package/pages/app/signup.vue +17 -0
- package/plugins/draggable.ts +5 -0
- package/plugins/edgeFirebaseFramework.ts +5 -0
- package/plugins/firebase.client.ts +16 -0
- package/plugins/maska.ts +5 -0
- package/plugins/number.ts +4 -0
- package/plugins/vuetify.ts +14 -0
- package/public/favicon.ico +0 -0
- package/public/images/logo-square.png +0 -0
- package/public/images/logo.png +0 -0
- package/server/tsconfig.json +3 -0
- package/storage.rules +8 -0
- package/tsconfig.json +4 -0
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
const props = defineProps({
|
|
3
|
+
docId: {
|
|
4
|
+
type: String,
|
|
5
|
+
default: '',
|
|
6
|
+
},
|
|
7
|
+
collection: {
|
|
8
|
+
type: String,
|
|
9
|
+
required: true,
|
|
10
|
+
},
|
|
11
|
+
newDocSchema: {
|
|
12
|
+
type: Object,
|
|
13
|
+
required: true,
|
|
14
|
+
},
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const newDoc = computed(() => {
|
|
18
|
+
return Object.entries(props.newDocSchema).reduce((newObj, [key, val]) => {
|
|
19
|
+
newObj[key] = val.value
|
|
20
|
+
return newObj
|
|
21
|
+
}, {})
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
const router = useRouter()
|
|
25
|
+
|
|
26
|
+
const state = reactive({
|
|
27
|
+
workingDoc: {},
|
|
28
|
+
form: false,
|
|
29
|
+
tab: 'forms',
|
|
30
|
+
bypassUnsavedChanges: false,
|
|
31
|
+
afterMount: false,
|
|
32
|
+
})
|
|
33
|
+
const edgeFirebase = inject('edgeFirebase')
|
|
34
|
+
const edgeGlobal = inject('edgeGlobal')
|
|
35
|
+
|
|
36
|
+
const unsavedChanges = computed(() => {
|
|
37
|
+
if (props.docId === 'new') {
|
|
38
|
+
return false
|
|
39
|
+
}
|
|
40
|
+
return JSON.stringify(state.workingDoc) !== JSON.stringify(edgeFirebase.data[`${edgeGlobal.edgeState.organizationDocPath}/${props.collection}`][props.docId])
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
const subCollection = (collection) => {
|
|
44
|
+
if (edgeGlobal.objHas(edgeFirebase.data, `${edgeGlobal.edgeState.organizationDocPath}/${collection}`) === false) {
|
|
45
|
+
return []
|
|
46
|
+
}
|
|
47
|
+
// need to return an array of objects title is name and value is docId
|
|
48
|
+
return Object.entries(edgeFirebase.data[`${edgeGlobal.edgeState.organizationDocPath}/${collection}`]).map(([key, val]) => {
|
|
49
|
+
return {
|
|
50
|
+
title: val.name,
|
|
51
|
+
value: key,
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
onBeforeRouteUpdate((to, from, next) => {
|
|
57
|
+
if (unsavedChanges.value && !state.bypassUnsavedChanges) {
|
|
58
|
+
state.dialog = true
|
|
59
|
+
next(false)
|
|
60
|
+
return
|
|
61
|
+
}
|
|
62
|
+
edgeGlobal.edgeState.changeTracker = {}
|
|
63
|
+
next()
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
const discardChanges = async () => {
|
|
67
|
+
if (props.docId === 'new') {
|
|
68
|
+
state.bypassUnsavedChanges = true
|
|
69
|
+
edgeGlobal.edgeState.changeTracker = {}
|
|
70
|
+
router.push('/app/dashboard')
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
state.workingDoc = await edgeGlobal.dupObject(edgeFirebase.data[`${edgeGlobal.edgeState.organizationDocPath}/${props.collection}`][props.docId])
|
|
74
|
+
state.bypassUnsavedChanges = true
|
|
75
|
+
edgeGlobal.edgeState.changeTracker = {}
|
|
76
|
+
router.push('/app/dashboard')
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const capitalizeFirstLetter = (str) => {
|
|
80
|
+
return str.charAt(0).toUpperCase() + str.slice(1)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const singularize = (word) => {
|
|
84
|
+
if (word.endsWith('ies')) {
|
|
85
|
+
return `${word.slice(0, -3)}y`
|
|
86
|
+
}
|
|
87
|
+
else if (word.endsWith('es')) {
|
|
88
|
+
return word.slice(0, -2)
|
|
89
|
+
}
|
|
90
|
+
else if (word.endsWith('s')) {
|
|
91
|
+
return word.slice(0, -1)
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
return word
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const title = computed(() => {
|
|
99
|
+
if (props.docId !== 'new') {
|
|
100
|
+
if (!edgeFirebase.data[`${edgeGlobal.edgeState.organizationDocPath}/${props.collection}`]) {
|
|
101
|
+
return ''
|
|
102
|
+
}
|
|
103
|
+
return capitalizeFirstLetter(`${edgeFirebase.data[`${edgeGlobal.edgeState.organizationDocPath}/${props.collection}`][props.docId].name}`)
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
return `New ${capitalizeFirstLetter(singularize(props.collection))}`
|
|
107
|
+
}
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
const onSubmit = async (event) => {
|
|
111
|
+
const results = await event
|
|
112
|
+
if (results.valid) {
|
|
113
|
+
state.bypassUnsavedChanges = true
|
|
114
|
+
edgeFirebase.storeDoc(`${edgeGlobal.edgeState.organizationDocPath}/${props.collection}`, state.workingDoc)
|
|
115
|
+
edgeGlobal.edgeState.changeTracker = {}
|
|
116
|
+
router.push(`/app/dashboard/${props.collection}`)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
onBeforeMount(async () => {
|
|
121
|
+
edgeGlobal.edgeState.changeTracker = {}
|
|
122
|
+
for (const field of Object.keys(props.newDocSchema)) {
|
|
123
|
+
if (props.newDocSchema[field].type === 'collection') {
|
|
124
|
+
await edgeFirebase.startSnapshot(`${edgeGlobal.edgeState.organizationDocPath}/${field}`)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
await edgeFirebase.startSnapshot(`${edgeGlobal.edgeState.organizationDocPath}/${props.collection}`)
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
watch(() => edgeFirebase.data[`${edgeGlobal.edgeState.organizationDocPath}/${props.collection}`], (newVal) => {
|
|
131
|
+
if (props.docId !== 'new') {
|
|
132
|
+
if (edgeGlobal.objHas(newVal, props.docId) === false) {
|
|
133
|
+
return
|
|
134
|
+
}
|
|
135
|
+
state.workingDoc = edgeGlobal.dupObject(newVal[props.docId])
|
|
136
|
+
Object.keys(newDoc.value).forEach((field) => {
|
|
137
|
+
if (!edgeGlobal.objHas(state.workingDoc, field)) {
|
|
138
|
+
state.workingDoc[field] = newDoc.value[field]
|
|
139
|
+
}
|
|
140
|
+
})
|
|
141
|
+
state.afterMount = true
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
state.workingDoc = edgeGlobal.dupObject(newDoc.value)
|
|
145
|
+
state.afterMount = true
|
|
146
|
+
}
|
|
147
|
+
})
|
|
148
|
+
</script>
|
|
149
|
+
|
|
150
|
+
<template>
|
|
151
|
+
<v-card v-if="state.afterMount">
|
|
152
|
+
<v-form
|
|
153
|
+
v-model="state.form"
|
|
154
|
+
validate-on="submit"
|
|
155
|
+
@submit.prevent="onSubmit"
|
|
156
|
+
>
|
|
157
|
+
<v-toolbar flat>
|
|
158
|
+
<v-icon class="mx-4">
|
|
159
|
+
mdi-atom-variant
|
|
160
|
+
</v-icon>
|
|
161
|
+
|
|
162
|
+
{{ title }}
|
|
163
|
+
<v-spacer />
|
|
164
|
+
<v-btn
|
|
165
|
+
type="submit"
|
|
166
|
+
color="primary"
|
|
167
|
+
variant="text"
|
|
168
|
+
>
|
|
169
|
+
Save
|
|
170
|
+
</v-btn>
|
|
171
|
+
</v-toolbar>
|
|
172
|
+
<v-card-text>
|
|
173
|
+
<v-row>
|
|
174
|
+
<v-col v-for="(field, name, index) in props.newDocSchema" :key="index" :cols="field.cols">
|
|
175
|
+
<g-input
|
|
176
|
+
v-if="field.type !== 'collection'"
|
|
177
|
+
v-model="state.workingDoc[name]"
|
|
178
|
+
:disable-tracking="props.docId === 'new'"
|
|
179
|
+
:field-type="field.type"
|
|
180
|
+
:rules="[edgeGlobal.edgeRules.required]"
|
|
181
|
+
:label="field.label"
|
|
182
|
+
:parent-tracker-id="`${props.collection}-${props.docId}`"
|
|
183
|
+
:helper="field.helper"
|
|
184
|
+
/>
|
|
185
|
+
<g-input
|
|
186
|
+
v-else
|
|
187
|
+
v-model="state.workingDoc[name]"
|
|
188
|
+
:disable-tracking="props.docId === 'new'"
|
|
189
|
+
field-type="select"
|
|
190
|
+
:label="field.label"
|
|
191
|
+
:items="subCollection(name)"
|
|
192
|
+
:parent-tracker-id="`${props.collection}-${props.docId}`"
|
|
193
|
+
/>
|
|
194
|
+
</v-col>
|
|
195
|
+
</v-row>
|
|
196
|
+
</v-card-text>
|
|
197
|
+
<v-card-actions>
|
|
198
|
+
<v-spacer />
|
|
199
|
+
<v-btn
|
|
200
|
+
v-if="!unsavedChanges"
|
|
201
|
+
color="secondary"
|
|
202
|
+
variant="text"
|
|
203
|
+
to="/app/dashboard"
|
|
204
|
+
>
|
|
205
|
+
Close
|
|
206
|
+
</v-btn>
|
|
207
|
+
<v-btn
|
|
208
|
+
v-else
|
|
209
|
+
color="secondary"
|
|
210
|
+
variant="text"
|
|
211
|
+
to="/app/dashboard"
|
|
212
|
+
>
|
|
213
|
+
Cancel
|
|
214
|
+
</v-btn>
|
|
215
|
+
|
|
216
|
+
<v-btn
|
|
217
|
+
type="submit"
|
|
218
|
+
color="primary"
|
|
219
|
+
variant="text"
|
|
220
|
+
>
|
|
221
|
+
Save
|
|
222
|
+
</v-btn>
|
|
223
|
+
</v-card-actions>
|
|
224
|
+
</v-form>
|
|
225
|
+
</v-card>
|
|
226
|
+
<v-dialog v-model="state.dialog" max-width="500px">
|
|
227
|
+
<v-card>
|
|
228
|
+
<v-card-title class="headline">
|
|
229
|
+
Unsaved Changes!
|
|
230
|
+
</v-card-title>
|
|
231
|
+
<v-card-text>
|
|
232
|
+
<h4>"{{ title }}" has unsaved changes.</h4>
|
|
233
|
+
<p>Are you sure you want to discard them?</p>
|
|
234
|
+
</v-card-text>
|
|
235
|
+
<v-card-actions>
|
|
236
|
+
<v-spacer />
|
|
237
|
+
<v-btn color="blue darken-1" text @click="state.dialog = false">
|
|
238
|
+
Cancel
|
|
239
|
+
</v-btn>
|
|
240
|
+
<v-btn color="error" text @click="discardChanges()">
|
|
241
|
+
Discard
|
|
242
|
+
</v-btn>
|
|
243
|
+
</v-card-actions>
|
|
244
|
+
</v-card>
|
|
245
|
+
</v-dialog>
|
|
246
|
+
</template>
|
|
247
|
+
|
|
248
|
+
<style lang="scss" scoped>
|
|
249
|
+
|
|
250
|
+
</style>
|
|
File without changes
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
const edgeFirebase = inject('edgeFirebase')
|
|
3
|
+
const edgeGlobal = inject('edgeGlobal')
|
|
4
|
+
</script>
|
|
5
|
+
|
|
6
|
+
<template>
|
|
7
|
+
<v-app-bar v-if="edgeFirebase.user.loggedIn">
|
|
8
|
+
<v-spacer />
|
|
9
|
+
<user-menu />
|
|
10
|
+
</v-app-bar>
|
|
11
|
+
</template>
|
|
12
|
+
|
|
13
|
+
<style lang="scss" scoped>
|
|
14
|
+
.inverted-logo {
|
|
15
|
+
filter: invert(1);
|
|
16
|
+
}
|
|
17
|
+
</style>
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
const edgeFirebase = inject('edgeFirebase')
|
|
3
|
+
const edgeGlobal = inject('edgeGlobal')
|
|
4
|
+
</script>
|
|
5
|
+
|
|
6
|
+
<template>
|
|
7
|
+
<v-menu>
|
|
8
|
+
<template #activator="{ props }">
|
|
9
|
+
<v-btn
|
|
10
|
+
prepend-icon="mdi-account-group-outline"
|
|
11
|
+
color="primary"
|
|
12
|
+
dark
|
|
13
|
+
v-bind="props"
|
|
14
|
+
variant="plain"
|
|
15
|
+
>
|
|
16
|
+
<template #prepend>
|
|
17
|
+
<v-icon color="secondary" />
|
|
18
|
+
</template>
|
|
19
|
+
{{ edgeGlobal.currentOrganizationObject.name }}
|
|
20
|
+
</v-btn>
|
|
21
|
+
</template>
|
|
22
|
+
<v-card>
|
|
23
|
+
<v-list>
|
|
24
|
+
<v-list-item
|
|
25
|
+
:title="edgeFirebase.user.meta.name"
|
|
26
|
+
:subtitle="edgeFirebase.user.firebaseUser.providerData[0].email"
|
|
27
|
+
>
|
|
28
|
+
<template #prepend>
|
|
29
|
+
<v-avatar>
|
|
30
|
+
<v-icon>
|
|
31
|
+
mdi-account
|
|
32
|
+
</v-icon>
|
|
33
|
+
</v-avatar>
|
|
34
|
+
</template>
|
|
35
|
+
<template #append>
|
|
36
|
+
<v-btn size="small" variant="text" icon="mdi-menu-down" />
|
|
37
|
+
</template>
|
|
38
|
+
</v-list-item>
|
|
39
|
+
</v-list>
|
|
40
|
+
<v-divider />
|
|
41
|
+
<edge-org-switcher />
|
|
42
|
+
|
|
43
|
+
<v-divider />
|
|
44
|
+
|
|
45
|
+
<v-list :lines="false" density="compact" nav>
|
|
46
|
+
<v-list-item link to="/app/account/organization-settings">
|
|
47
|
+
<v-list-item-title>Manage Account</v-list-item-title>
|
|
48
|
+
</v-list-item>
|
|
49
|
+
|
|
50
|
+
<v-list-item link to="/app/account/my-profile">
|
|
51
|
+
<v-list-item-title>My Profile</v-list-item-title>
|
|
52
|
+
</v-list-item>
|
|
53
|
+
</v-list>
|
|
54
|
+
|
|
55
|
+
<v-divider />
|
|
56
|
+
|
|
57
|
+
<v-list :lines="false" density="compact" nav>
|
|
58
|
+
<v-list-item @click="logOut(edgeFirebase, edgeGlobal)">
|
|
59
|
+
<v-list-item-title>Log out</v-list-item-title>
|
|
60
|
+
</v-list-item>
|
|
61
|
+
</v-list>
|
|
62
|
+
</v-card>
|
|
63
|
+
</v-menu>
|
|
64
|
+
</template>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const globalState = reactive({
|
|
2
|
+
drawer: false,
|
|
3
|
+
dark: true,
|
|
4
|
+
})
|
|
5
|
+
|
|
6
|
+
export const projectSetOrg = async (organization: string, edgeFirebase: any, edgeGlobal: any) => {
|
|
7
|
+
// set Organization Paths and start snapshots here
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const logOut = async (edgeFirebase: any, edgeGlobal: any) => {
|
|
11
|
+
const auth = useState('auth')
|
|
12
|
+
auth.value = ''
|
|
13
|
+
globalState.drawer = false
|
|
14
|
+
await edgeGlobal.edgeLogOut(edgeFirebase)
|
|
15
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// DO NOT CHANGE THIS FILE
|
|
2
|
+
import { getCurrentInstance } from 'vue'
|
|
3
|
+
|
|
4
|
+
export function useVuetify() {
|
|
5
|
+
const instance: any = getCurrentInstance()
|
|
6
|
+
if (!instance) {
|
|
7
|
+
throw new Error('useVuetify should be called in setup().')
|
|
8
|
+
}
|
|
9
|
+
return instance.proxy.$vuetify
|
|
10
|
+
}
|
package/deploy.sh
ADDED
package/emulator.sh
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
ports=(9099 4000 5001 8080 5025 9199)
|
|
4
|
+
for port in "${ports[@]}"; do
|
|
5
|
+
pid=$(lsof -ti :$port)
|
|
6
|
+
if [ ! -z "$pid" ]; then
|
|
7
|
+
process_name=$(ps -p $pid -o comm=)
|
|
8
|
+
echo $process_name
|
|
9
|
+
echo "Stopping Firebase emulator on port $port with PID $pid"
|
|
10
|
+
kill -9 $pid
|
|
11
|
+
fi
|
|
12
|
+
done
|
|
13
|
+
DIR="./firebase_data"
|
|
14
|
+
if [ ! -d "$DIR" ]; then
|
|
15
|
+
cp -r ./firebase_data_emulator_seed ./firebase_data
|
|
16
|
+
fi
|
|
17
|
+
firebase emulators:start --import ./firebase_data --export-on-exit
|
package/firebase.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"firestore": {
|
|
3
|
+
"rules": "firestore.rules",
|
|
4
|
+
"indexes": "firestore.indexes.json"
|
|
5
|
+
},
|
|
6
|
+
"functions": [
|
|
7
|
+
{
|
|
8
|
+
"source": "functions",
|
|
9
|
+
"codebase": "default",
|
|
10
|
+
"ignore": [
|
|
11
|
+
"node_modules",
|
|
12
|
+
".git",
|
|
13
|
+
"firebase-debug.log",
|
|
14
|
+
"firebase-debug.*.log"
|
|
15
|
+
]
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
"hosting": {
|
|
19
|
+
"public": ".output/public",
|
|
20
|
+
"ignore": [
|
|
21
|
+
"firebase.json",
|
|
22
|
+
"**/.*",
|
|
23
|
+
"**/node_modules/**"
|
|
24
|
+
],
|
|
25
|
+
"rewrites": [
|
|
26
|
+
{
|
|
27
|
+
"source": "**",
|
|
28
|
+
"destination": "/index.html"
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
},
|
|
32
|
+
"storage": {
|
|
33
|
+
"rules": "storage.rules"
|
|
34
|
+
},
|
|
35
|
+
"emulators": {
|
|
36
|
+
"auth": {
|
|
37
|
+
"port": 9099
|
|
38
|
+
},
|
|
39
|
+
"functions": {
|
|
40
|
+
"port": 5001
|
|
41
|
+
},
|
|
42
|
+
"firestore": {
|
|
43
|
+
"port": 8080
|
|
44
|
+
},
|
|
45
|
+
"hosting": {
|
|
46
|
+
"port": 5025
|
|
47
|
+
},
|
|
48
|
+
"storage": {
|
|
49
|
+
"port": 9199
|
|
50
|
+
},
|
|
51
|
+
"ui": {
|
|
52
|
+
"enabled": true
|
|
53
|
+
},
|
|
54
|
+
"singleProjectMode": true
|
|
55
|
+
}
|
|
56
|
+
}
|