added ImportPopup
This commit is contained in:
		
							parent
							
								
									93ab5fbea2
								
							
						
					
					
						commit
						4e58e7ad6a
					
				|  | @ -169,6 +169,7 @@ func (api *collectionApi) delete(c echo.Context) error { | |||
| 	return handlerErr | ||||
| } | ||||
| 
 | ||||
| // @todo add event
 | ||||
| func (api *collectionApi) bulkImport(c echo.Context) error { | ||||
| 	form := forms.NewCollectionsImport(api.app) | ||||
| 
 | ||||
|  | @ -182,5 +183,5 @@ func (api *collectionApi) bulkImport(c echo.Context) error { | |||
| 		return rest.NewBadRequestError("Failed to import the submitted collections.", submitErr) | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| 	return c.NoContent(http.StatusNoContent) | ||||
| } | ||||
|  |  | |||
|  | @ -1,6 +1,7 @@ | |||
| package forms | ||||
| 
 | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"log" | ||||
| 
 | ||||
|  | @ -59,24 +60,8 @@ func (form *CollectionsImport) Submit() error { | |||
| 			mappedOldCollections[old.GetId()] = old | ||||
| 		} | ||||
| 
 | ||||
| 		// raw insert/replace (aka. without any validations)
 | ||||
| 		// (required to make sure that all linked collections exists before running the validations)
 | ||||
| 		mappedFormCollections := make(map[string]*models.Collection, len(form.Collections)) | ||||
| 		for _, collection := range form.Collections { | ||||
| 			if mappedOldCollections[collection.GetId()] == nil { | ||||
| 				collection.MarkAsNew() | ||||
| 			} | ||||
| 
 | ||||
| 			if err := txDao.Save(collection); err != nil { | ||||
| 				if form.app.IsDebug() { | ||||
| 					log.Println("[CollectionsImport] Save failure", collection.Name, err) | ||||
| 				} | ||||
| 				return validation.Errors{"collections": validation.NewError( | ||||
| 					"collections_import_save_failure", | ||||
| 					fmt.Sprintf("Failed to save the imported collection %q (id: %s).", collection.Name, collection.Id), | ||||
| 				)} | ||||
| 			} | ||||
| 
 | ||||
| 			mappedFormCollections[collection.GetId()] = collection | ||||
| 		} | ||||
| 
 | ||||
|  | @ -89,15 +74,33 @@ func (form *CollectionsImport) Submit() error { | |||
| 						if form.app.IsDebug() { | ||||
| 							log.Println("[CollectionsImport] DeleteOthers failure", old.Name, err) | ||||
| 						} | ||||
| 						return validation.Errors{"deleteOthers": validation.NewError( | ||||
| 						return validation.Errors{"collections": validation.NewError( | ||||
| 							"collections_import_collection_delete_failure", | ||||
| 							fmt.Sprintf("Failed to delete collection %q. Make sure that the collection is not system or referenced by other collections.", old.Name), | ||||
| 							fmt.Sprintf("Failed to delete collection %q (%s). Make sure that the collection is not system or referenced by other collections.", old.Name, old.Id), | ||||
| 						)} | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		// raw insert/replace (aka. without any validations)
 | ||||
| 		// (required to make sure that all linked collections exists before running the validations)
 | ||||
| 		for _, collection := range form.Collections { | ||||
| 			if mappedOldCollections[collection.GetId()] == nil { | ||||
| 				collection.MarkAsNew() | ||||
| 			} | ||||
| 
 | ||||
| 			if err := txDao.Save(collection); err != nil { | ||||
| 				if form.app.IsDebug() { | ||||
| 					log.Println("[CollectionsImport] Save failure", collection.Name, err) | ||||
| 				} | ||||
| 				return validation.Errors{"collections": validation.NewError( | ||||
| 					"collections_import_save_failure", | ||||
| 					fmt.Sprintf("Integrity constraints failed - the collection %q (%s) cannot be imported.", collection.Name, collection.Id), | ||||
| 				)} | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		// refresh the actual persisted collections list
 | ||||
| 		refreshedCollections := []*models.Collection{} | ||||
| 		if err := txDao.CollectionQuery().All(&refreshedCollections); err != nil { | ||||
|  | @ -125,9 +128,13 @@ func (form *CollectionsImport) Submit() error { | |||
| 				if form.app.IsDebug() { | ||||
| 					log.Println("[CollectionsImport] Validate failure", collection.Name, err) | ||||
| 				} | ||||
| 
 | ||||
| 				// serialize the validation error(s)
 | ||||
| 				serializedErr, _ := json.Marshal(err) | ||||
| 
 | ||||
| 				return validation.Errors{"collections": validation.NewError( | ||||
| 					"collections_import_validate_failure", | ||||
| 					fmt.Sprintf("Integrity check failed - collection %q has invalid data.", collection.Name), | ||||
| 					fmt.Sprintf("Data validations failed for collection %q (%s): %s", collection.Name, collection.Id, serializedErr), | ||||
| 				)} | ||||
| 			} | ||||
| 		} | ||||
|  |  | |||
|  | @ -9,7 +9,6 @@ | |||
|         "@codemirror/autocomplete": "^6.0.0", | ||||
|         "@codemirror/commands": "^6.0.0", | ||||
|         "@codemirror/lang-javascript": "^6.0.2", | ||||
|         "@codemirror/lang-json": "^6.0.0", | ||||
|         "@codemirror/language": "^6.0.0", | ||||
|         "@codemirror/legacy-modes": "^6.0.0", | ||||
|         "@codemirror/search": "^6.0.0", | ||||
|  | @ -29,8 +28,7 @@ | |||
|       } | ||||
|     }, | ||||
|     "../../js-sdk": { | ||||
|       "name": "pocketbase", | ||||
|       "version": "0.3.0", | ||||
|       "version": "0.3.1", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "devDependencies": { | ||||
|  | @ -101,16 +99,6 @@ | |||
|         "@lezer/javascript": "^1.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@codemirror/lang-json": { | ||||
|       "version": "6.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.0.tgz", | ||||
|       "integrity": "sha512-DvTcYTKLmg2viADXlTdufrT334M9jowe1qO02W28nvm+nejcvhM5vot5mE8/kPrxYw/HJHhwu1z2PyBpnMLCNQ==", | ||||
|       "dev": true, | ||||
|       "dependencies": { | ||||
|         "@codemirror/language": "^6.0.0", | ||||
|         "@lezer/json": "^1.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@codemirror/language": { | ||||
|       "version": "6.2.1", | ||||
|       "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.2.1.tgz", | ||||
|  | @ -163,9 +151,9 @@ | |||
|       "dev": true | ||||
|     }, | ||||
|     "node_modules/@codemirror/view": { | ||||
|       "version": "6.1.4", | ||||
|       "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.1.4.tgz", | ||||
|       "integrity": "sha512-pekgUX+0hL4ri2JV7/bu7jhhwOgOhU1eRc1/ZyAQYCWcCI4TPB1qLrPE3cD/qW9yjBcYiN9MN0XI1tjK7Yw05Q==", | ||||
|       "version": "6.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.2.0.tgz", | ||||
|       "integrity": "sha512-3emW1symh+GoteFMBPsltjmF790U/trouLILATh3JodbF/z98HvcQh2g3+H6dfNIHx16uNonsAF4mNzVr1TJNA==", | ||||
|       "dev": true, | ||||
|       "dependencies": { | ||||
|         "@codemirror/state": "^6.0.0", | ||||
|  | @ -214,16 +202,6 @@ | |||
|         "@lezer/lr": "^1.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@lezer/json": { | ||||
|       "version": "1.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.0.tgz", | ||||
|       "integrity": "sha512-zbAuUY09RBzCoCA3lJ1+ypKw5WSNvLqGMtasdW6HvVOqZoCpPr8eWrsGnOVWGKGn8Rh21FnrKRVlJXrGAVUqRw==", | ||||
|       "dev": true, | ||||
|       "dependencies": { | ||||
|         "@lezer/highlight": "^1.0.0", | ||||
|         "@lezer/lr": "^1.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@lezer/lr": { | ||||
|       "version": "1.2.1", | ||||
|       "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.2.1.tgz", | ||||
|  | @ -1038,9 +1016,9 @@ | |||
|       } | ||||
|     }, | ||||
|     "node_modules/sass": { | ||||
|       "version": "1.54.2", | ||||
|       "resolved": "https://registry.npmjs.org/sass/-/sass-1.54.2.tgz", | ||||
|       "integrity": "sha512-wbVV26sejsCIbBScZZtNkvnrB/bVCQ8hSlZ01D9nzsVh9zLqCkWrlpvTb3YEb6xsuNi9cx75hncqwikHFSz7tw==", | ||||
|       "version": "1.54.3", | ||||
|       "resolved": "https://registry.npmjs.org/sass/-/sass-1.54.3.tgz", | ||||
|       "integrity": "sha512-fLodey5Qd41Pxp/Tk7Al97sViYwF/TazRc5t6E65O7JOk4XF8pzwIW7CvCxYVOfJFFI/1x5+elDyBIixrp+zrw==", | ||||
|       "dev": true, | ||||
|       "dependencies": { | ||||
|         "chokidar": ">=3.0.0 <4.0.0", | ||||
|  | @ -1232,16 +1210,6 @@ | |||
|         "@lezer/javascript": "^1.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "@codemirror/lang-json": { | ||||
|       "version": "6.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.0.tgz", | ||||
|       "integrity": "sha512-DvTcYTKLmg2viADXlTdufrT334M9jowe1qO02W28nvm+nejcvhM5vot5mE8/kPrxYw/HJHhwu1z2PyBpnMLCNQ==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "@codemirror/language": "^6.0.0", | ||||
|         "@lezer/json": "^1.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "@codemirror/language": { | ||||
|       "version": "6.2.1", | ||||
|       "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.2.1.tgz", | ||||
|  | @ -1294,9 +1262,9 @@ | |||
|       "dev": true | ||||
|     }, | ||||
|     "@codemirror/view": { | ||||
|       "version": "6.1.4", | ||||
|       "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.1.4.tgz", | ||||
|       "integrity": "sha512-pekgUX+0hL4ri2JV7/bu7jhhwOgOhU1eRc1/ZyAQYCWcCI4TPB1qLrPE3cD/qW9yjBcYiN9MN0XI1tjK7Yw05Q==", | ||||
|       "version": "6.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.2.0.tgz", | ||||
|       "integrity": "sha512-3emW1symh+GoteFMBPsltjmF790U/trouLILATh3JodbF/z98HvcQh2g3+H6dfNIHx16uNonsAF4mNzVr1TJNA==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "@codemirror/state": "^6.0.0", | ||||
|  | @ -1336,16 +1304,6 @@ | |||
|         "@lezer/lr": "^1.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "@lezer/json": { | ||||
|       "version": "1.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.0.tgz", | ||||
|       "integrity": "sha512-zbAuUY09RBzCoCA3lJ1+ypKw5WSNvLqGMtasdW6HvVOqZoCpPr8eWrsGnOVWGKGn8Rh21FnrKRVlJXrGAVUqRw==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "@lezer/highlight": "^1.0.0", | ||||
|         "@lezer/lr": "^1.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "@lezer/lr": { | ||||
|       "version": "1.2.1", | ||||
|       "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.2.1.tgz", | ||||
|  | @ -1855,9 +1813,9 @@ | |||
|       } | ||||
|     }, | ||||
|     "sass": { | ||||
|       "version": "1.54.2", | ||||
|       "resolved": "https://registry.npmjs.org/sass/-/sass-1.54.2.tgz", | ||||
|       "integrity": "sha512-wbVV26sejsCIbBScZZtNkvnrB/bVCQ8hSlZ01D9nzsVh9zLqCkWrlpvTb3YEb6xsuNi9cx75hncqwikHFSz7tw==", | ||||
|       "version": "1.54.3", | ||||
|       "resolved": "https://registry.npmjs.org/sass/-/sass-1.54.3.tgz", | ||||
|       "integrity": "sha512-fLodey5Qd41Pxp/Tk7Al97sViYwF/TazRc5t6E65O7JOk4XF8pzwIW7CvCxYVOfJFFI/1x5+elDyBIixrp+zrw==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "chokidar": ">=3.0.0 <4.0.0", | ||||
|  |  | |||
|  | @ -1,134 +0,0 @@ | |||
| <script> | ||||
|     import { onMount, createEventDispatcher } from "svelte"; | ||||
|     // code mirror imports | ||||
|     // --- | ||||
|     import { | ||||
|         keymap, | ||||
|         highlightSpecialChars, | ||||
|         drawSelection, | ||||
|         dropCursor, | ||||
|         rectangularSelection, | ||||
|         EditorView, | ||||
|         placeholder as placeholderExt, | ||||
|     } from "@codemirror/view"; | ||||
|     import { EditorState, Compartment } from "@codemirror/state"; | ||||
|     import { defaultHighlightStyle, syntaxHighlighting, bracketMatching } from "@codemirror/language"; | ||||
|     import { defaultKeymap, history, historyKeymap, indentWithTab } from "@codemirror/commands"; | ||||
|     import { searchKeymap, highlightSelectionMatches } from "@codemirror/search"; | ||||
|     import { closeBrackets, closeBracketsKeymap } from "@codemirror/autocomplete"; | ||||
|     import { javascript } from "@codemirror/lang-javascript"; | ||||
|     // --- | ||||
| 
 | ||||
|     const dispatch = createEventDispatcher(); | ||||
| 
 | ||||
|     export let value = ""; | ||||
|     export let disabled = false; | ||||
|     export let placeholder = ""; | ||||
|     export let singleLine = false; | ||||
| 
 | ||||
|     let editor; | ||||
|     let container; | ||||
|     let editableCompartment = new Compartment(); | ||||
|     let readOnlyCompartment = new Compartment(); | ||||
|     let placeholderCompartment = new Compartment(); | ||||
| 
 | ||||
|     $: if (editor && typeof disabled !== "undefined") { | ||||
|         editor.dispatch({ | ||||
|             effects: [ | ||||
|                 editableCompartment.reconfigure(EditorView.editable.of(!disabled)), | ||||
|                 readOnlyCompartment.reconfigure(EditorState.readOnly.of(disabled)), | ||||
|             ], | ||||
|         }); | ||||
| 
 | ||||
|         triggerNativeChange(); | ||||
|     } | ||||
| 
 | ||||
|     $: if (editor && value != editor.state.doc.toString()) { | ||||
|         editor.dispatch({ | ||||
|             changes: { | ||||
|                 from: 0, | ||||
|                 to: editor.state.doc.length, | ||||
|                 insert: value, | ||||
|             }, | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     $: if (editor && typeof placeholder !== "undefined") { | ||||
|         editor.dispatch({ | ||||
|             effects: [placeholderCompartment.reconfigure(placeholderExt(placeholder))], | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     // Focus the editor (if inited). | ||||
|     export function focus() { | ||||
|         editor?.focus(); | ||||
|     } | ||||
| 
 | ||||
|     // Emulate native change event for the editor container element. | ||||
|     function triggerNativeChange() { | ||||
|         container?.dispatchEvent( | ||||
|             new CustomEvent("change", { | ||||
|                 detail: { value }, | ||||
|                 bubbles: true, | ||||
|             }) | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     onMount(() => { | ||||
|         const submitShortcut = { | ||||
|             key: "Enter", | ||||
|             run: (_) => { | ||||
|                 // trigger submit on enter for singleline input | ||||
|                 if (singleLine) { | ||||
|                     dispatch("submit", value); | ||||
|                 } | ||||
|             }, | ||||
|         }; | ||||
| 
 | ||||
|         editor = new EditorView({ | ||||
|             parent: container, | ||||
|             state: EditorState.create({ | ||||
|                 doc: value, | ||||
|                 extensions: [ | ||||
|                     highlightSpecialChars(), | ||||
|                     history(), | ||||
|                     drawSelection(), | ||||
|                     dropCursor(), | ||||
|                     EditorState.allowMultipleSelections.of(true), | ||||
|                     syntaxHighlighting(defaultHighlightStyle, { fallback: true }), | ||||
|                     bracketMatching(), | ||||
|                     closeBrackets(), | ||||
|                     rectangularSelection(), | ||||
|                     highlightSelectionMatches(), | ||||
|                     keymap.of([ | ||||
|                         submitShortcut, | ||||
|                         indentWithTab, | ||||
|                         ...closeBracketsKeymap, | ||||
|                         ...defaultKeymap, | ||||
|                         ...searchKeymap, | ||||
|                         ...historyKeymap, | ||||
|                     ]), | ||||
|                     EditorView.lineWrapping, | ||||
|                     javascript(), | ||||
|                     placeholderCompartment.of(placeholderExt(placeholder)), | ||||
|                     editableCompartment.of(EditorView.editable.of(true)), | ||||
|                     readOnlyCompartment.of(EditorState.readOnly.of(false)), | ||||
|                     EditorState.transactionFilter.of((tr) => { | ||||
|                         return singleLine && tr.newDoc.lines > 1 ? [] : tr; | ||||
|                     }), | ||||
|                     EditorView.updateListener.of((v) => { | ||||
|                         if (!v.docChanged || disabled) { | ||||
|                             return; | ||||
|                         } | ||||
|                         value = v.state.doc.toString(); | ||||
|                         triggerNativeChange(); | ||||
|                     }), | ||||
|                 ], | ||||
|             }), | ||||
|         }); | ||||
| 
 | ||||
|         return () => editor?.destroy(); | ||||
|     }); | ||||
| </script> | ||||
| 
 | ||||
| <div bind:this={container} class="code-editor" /> | ||||
|  | @ -1,352 +0,0 @@ | |||
| <script> | ||||
|     import ApiClient from "@/utils/ApiClient"; | ||||
|     import CommonHelper from "@/utils/CommonHelper"; | ||||
|     import Field from "@/components/base/Field.svelte"; | ||||
|     import CodeBlock from "@/components/base/CodeBlock.svelte"; | ||||
|     import { onMount } from "svelte"; | ||||
| 
 | ||||
|     const uniqueId = "exports_" + CommonHelper.randomString(5); | ||||
| 
 | ||||
|     let collections = []; | ||||
|     let isLoadingCollections = false; | ||||
| 
 | ||||
|     loadCollections(); | ||||
| 
 | ||||
|     async function loadCollections() { | ||||
|         isLoadingCollections = true; | ||||
| 
 | ||||
|         try { | ||||
|             collections = await ApiClient.collections.getFullList(100, { | ||||
|                 $cancelKey: uniqueId, | ||||
|             }); | ||||
|         } catch (err) { | ||||
|             ApiClient.errorResponseHandler(err); | ||||
|         } | ||||
| 
 | ||||
|         isLoadingCollections = false; | ||||
|     } | ||||
| 
 | ||||
|     let oldSchema = ""; | ||||
|     let newSchema = ""; | ||||
| 
 | ||||
|     function diff_prettyHtml(diffs, showInsert) { | ||||
|         const html = []; | ||||
|         const pattern_amp = /&/g; | ||||
|         const pattern_lt = /</g; | ||||
|         const pattern_gt = />/g; | ||||
|         const pattern_para = /\n/g; | ||||
|         for (let x = 0; x < diffs.length; x++) { | ||||
|             let op = diffs[x][0]; // Operation (insert, delete, equal) | ||||
|             let data = diffs[x][1]; // Text of change. | ||||
|             let text = data | ||||
|                 .replace(pattern_amp, "&") | ||||
|                 .replace(pattern_lt, "<") | ||||
|                 .replace(pattern_gt, ">") | ||||
|                 .replace(pattern_para, "<br>"); | ||||
|             // text = CommonHelper.stripTags(text); | ||||
|             switch (op) { | ||||
|                 case DIFF_INSERT: | ||||
|                     if (showInsert) { | ||||
|                         html[x] = '<ins class="block">' + text + "</ins>"; | ||||
|                     } | ||||
|                     break; | ||||
|                 case DIFF_DELETE: | ||||
|                     if (!showInsert) { | ||||
|                         html[x] = '<del class="block">' + text + "</del>"; | ||||
|                     } | ||||
|                     break; | ||||
|                 case DIFF_EQUAL: | ||||
|                     html[x] = "<span>" + text + "</span>"; | ||||
|                     break; | ||||
|             } | ||||
|         } | ||||
|         return html.join(""); | ||||
|     } | ||||
| 
 | ||||
|     onMount(() => { | ||||
|         var dmp = new diff_match_patch(); | ||||
|         const text1 = [ | ||||
|             { | ||||
|                 id: "zwWlxR46txtoAwx", | ||||
|                 created: "2022-08-01 17:32:24.329", | ||||
|                 updated: "2022-08-04 10:19:57.248", | ||||
|                 name: "profia sdles", | ||||
|                 system: true, | ||||
|                 listRule: "userId = @request.user.id", | ||||
|                 viewRule: "userId = @request.user.id", | ||||
|                 createRule: "userId = @request.user.id", | ||||
|                 updateRule: "userId = @request.user.id", | ||||
|                 deleteRule: null, | ||||
|                 schema: [ | ||||
|                     { | ||||
|                         id: "nsght7oy", | ||||
|                         name: "userId", | ||||
|                         type: "user", | ||||
|                         system: true, | ||||
|                         required: true, | ||||
|                         unique: true, | ||||
|                         options: { | ||||
|                             maxSelect: 1, | ||||
|                             cascadeDelete: true, | ||||
|                         }, | ||||
|                     }, | ||||
|                     { | ||||
|                         id: "atpc4yjm", | ||||
|                         name: "name", | ||||
|                         type: "text", | ||||
|                         system: false, | ||||
|                         required: false, | ||||
|                         unique: false, | ||||
|                         options: { | ||||
|                             min: null, | ||||
|                             max: null, | ||||
|                             pattern: "", | ||||
|                         }, | ||||
|                     }, | ||||
|                     { | ||||
|                         id: "akb4s9de", | ||||
|                         name: "avatar", | ||||
|                         type: "file", | ||||
|                         system: false, | ||||
|                         required: false, | ||||
|                         unique: false, | ||||
|                         options: { | ||||
|                             maxSelect: 1, | ||||
|                             maxSize: 5242880, | ||||
|                             mimeTypes: ["image/jpg", "image/jpeg", "image/png", "image/svg+xml", "image/gif"], | ||||
|                             thumbs: null, | ||||
|                         }, | ||||
|                     }, | ||||
|                 ], | ||||
|             }, | ||||
|             { | ||||
|                 id: "IV8FbE78jmXF56d", | ||||
|                 created: "", | ||||
|                 updated: "2022-08-04 10:21:54.100", | ||||
|                 name: "abc", | ||||
|                 system: false, | ||||
|                 listRule: null, | ||||
|                 viewRule: null, | ||||
|                 createRule: null, | ||||
|                 updateRule: null, | ||||
|                 deleteRule: null, | ||||
|                 schema: [ | ||||
|                     { | ||||
|                         id: "t2pukeas", | ||||
|                         name: "demo", | ||||
|                         type: "text", | ||||
|                         system: false, | ||||
|                         required: false, | ||||
|                         unique: false, | ||||
|                         options: { | ||||
|                             min: null, | ||||
|                             max: null, | ||||
|                             pattern: "", | ||||
|                         }, | ||||
|                     }, | ||||
|                     { | ||||
|                         id: "dddddd", | ||||
|                         name: "aaaa", | ||||
|                         type: "text", | ||||
|                         system: false, | ||||
|                         required: false, | ||||
|                         unique: false, | ||||
|                         options: { | ||||
|                             min: null, | ||||
|                             max: null, | ||||
|                             pattern: "", | ||||
|                         }, | ||||
|                     }, | ||||
|                     { | ||||
|                         id: "squmamtm", | ||||
|                         name: "test", | ||||
|                         type: "date", | ||||
|                         system: false, | ||||
|                         required: false, | ||||
|                         unique: false, | ||||
|                         options: { | ||||
|                             min: "", | ||||
|                             max: "", | ||||
|                         }, | ||||
|                     }, | ||||
|                 ], | ||||
|             }, | ||||
|         ]; | ||||
| 
 | ||||
|         const text2 = [ | ||||
|             { | ||||
|                 id: "zwWlxR46txtoAwx", | ||||
|                 created: "2022-08-01 17:32:24.329", | ||||
|                 updated: "2022-08-04 10:19:57.248", | ||||
|                 name: "Demo", | ||||
|                 system: true, | ||||
|                 listRule: "userId = @request.user.id", | ||||
|                 viewRule: "userId = @request.user.id", | ||||
|                 createRule: "userId = @request.user.id", | ||||
|                 updateRule: "userId = @request.user.id", | ||||
|                 deleteRule: null, | ||||
|                 schema: [ | ||||
|                     { | ||||
|                         id: "nsght7oy", | ||||
|                         name: "userId", | ||||
|                         type: "user", | ||||
|                         system: true, | ||||
|                         required: true, | ||||
|                         unique: true, | ||||
|                         options: { | ||||
|                             maxSelect: 1, | ||||
|                             cascadeDelete: true, | ||||
|                         }, | ||||
|                     }, | ||||
|                     { | ||||
|                         id: "atpc4yjm", | ||||
|                         name: "name", | ||||
|                         type: "text", | ||||
|                         system: false, | ||||
|                         required: false, | ||||
|                         unique: false, | ||||
|                         options: { | ||||
|                             min: null, | ||||
|                             max: null, | ||||
|                             pattern: "", | ||||
|                         }, | ||||
|                     }, | ||||
|                     { | ||||
|                         id: "akb4s9de", | ||||
|                         name: "avatar", | ||||
|                         type: "file", | ||||
|                         system: false, | ||||
|                         required: false, | ||||
|                         unique: true, | ||||
|                         options: { | ||||
|                             maxSelect: 1, | ||||
|                             maxSize: 5242880, | ||||
|                             mimeTypes: ["image/jpg", "image/jpeg", "image/png", "image/svg+xml", "image/gif"], | ||||
|                             thumbs: null, | ||||
|                         }, | ||||
|                     }, | ||||
|                 ], | ||||
|             }, | ||||
|             { | ||||
|                 id: "IV8FbE78jmXF56d", | ||||
|                 created: "", | ||||
|                 updated: "2022-08-04 10:21:54.100", | ||||
|                 name: "abc", | ||||
|                 system: false, | ||||
|                 listRule: null, | ||||
|                 viewRule: null, | ||||
|                 createRule: null, | ||||
|                 updateRule: null, | ||||
|                 deleteRule: null, | ||||
|                 schema: [ | ||||
|                     { | ||||
|                         id: "t2pukeas", | ||||
|                         name: "demo", | ||||
|                         type: "text", | ||||
|                         system: false, | ||||
|                         required: false, | ||||
|                         unique: false, | ||||
|                         options: { | ||||
|                             min: null, | ||||
|                             max: null, | ||||
|                             pattern: "", | ||||
|                         }, | ||||
|                     }, | ||||
|                     { | ||||
|                         id: "dddddd", | ||||
|                         name: "aaaa", | ||||
|                         type: "text", | ||||
|                         system: false, | ||||
|                         required: false, | ||||
|                         unique: false, | ||||
|                         options: { | ||||
|                             min: null, | ||||
|                             max: null, | ||||
|                             pattern: "", | ||||
|                         }, | ||||
|                     }, | ||||
|                     { | ||||
|                         id: "squmamtm", | ||||
|                         name: "test", | ||||
|                         type: "date", | ||||
|                         system: false, | ||||
|                         required: false, | ||||
|                         unique: false, | ||||
|                         options: { | ||||
|                             min: "", | ||||
|                             max: "", | ||||
|                         }, | ||||
|                     }, | ||||
|                 ], | ||||
|             }, | ||||
|             { | ||||
|                 id: "GGACt8sa1tcJp7T", | ||||
|                 created: "2022-08-04 10:22:15.871", | ||||
|                 updated: "2022-08-04 10:22:15.871", | ||||
|                 name: "asdasd", | ||||
|                 system: true, | ||||
|                 listRule: null, | ||||
|                 viewRule: null, | ||||
|                 createRule: null, | ||||
|                 updateRule: null, | ||||
|                 deleteRule: null, | ||||
|                 schema: [ | ||||
|                     { | ||||
|                         id: "0eklwfvl", | ||||
|                         name: "field", | ||||
|                         type: "text", | ||||
|                         system: false, | ||||
|                         required: false, | ||||
|                         unique: false, | ||||
|                         options: { | ||||
|                             min: null, | ||||
|                             max: null, | ||||
|                             pattern: "", | ||||
|                         }, | ||||
|                     }, | ||||
|                 ], | ||||
|             }, | ||||
|         ]; | ||||
| 
 | ||||
|         // var diffs = dmp.diff_main(JSON.stringify(text1, null, 2), JSON.stringify(text2, null, 2)); | ||||
| 
 | ||||
|         var a = dmp.diff_linesToChars_(JSON.stringify(text1, null, 2), JSON.stringify(text2, null, 2)); | ||||
|         var lineText1 = a.chars1; | ||||
|         var lineText2 = a.chars2; | ||||
|         var lineArray = a.lineArray; | ||||
|         var diffs = dmp.diff_main(lineText1, lineText2, false); | ||||
|         dmp.diff_charsToLines_(diffs, lineArray); | ||||
| 
 | ||||
|         oldSchema = diff_prettyHtml(diffs, false); | ||||
|         newSchema = diff_prettyHtml(diffs, true); | ||||
|     }); | ||||
| </script> | ||||
| 
 | ||||
| <br /> | ||||
| <div class="grid"> | ||||
|     <div class="col-6"> | ||||
|         <code> | ||||
|             {@html oldSchema} | ||||
|         </code> | ||||
|     </div> | ||||
|     <div class="col-6"> | ||||
|         <code> | ||||
|             {@html newSchema} | ||||
|         </code> | ||||
|     </div> | ||||
| </div> | ||||
| 
 | ||||
| <style lang="scss"> | ||||
|     .collections-list { | ||||
|         column-count: 2; | ||||
|         column-gap: var(--baseSpacing); | ||||
|     } | ||||
|     code { | ||||
|         display: block; | ||||
|         width: 100%; | ||||
|         overflow: auto; | ||||
|         padding: var(--xsSpacing); | ||||
|         white-space: pre; | ||||
|         background: var(--baseAlt1Color); | ||||
|     } | ||||
| </style> | ||||
|  | @ -1,29 +0,0 @@ | |||
| <script> | ||||
|     import OverlayPanel from "@/components/base/OverlayPanel.svelte"; | ||||
|     import CollectionsExportForm from "@/components/collections/CollectionsExportForm.svelte"; | ||||
| 
 | ||||
|     let overlayPanel; | ||||
| 
 | ||||
|     export function show() { | ||||
|         return overlayPanel?.show(); | ||||
|     } | ||||
| 
 | ||||
|     export function hide() { | ||||
|         return overlayPanel?.hide(); | ||||
|     } | ||||
| </script> | ||||
| 
 | ||||
| <OverlayPanel | ||||
|     bind:this={overlayPanel} | ||||
|     class="overlay-panel-xl collections-export-panel" | ||||
|     on:hide | ||||
|     on:show | ||||
|     popup | ||||
|     active | ||||
| > | ||||
|     <svelte:fragment slot="header"> | ||||
|         <h4>Export collections schema</h4> | ||||
|     </svelte:fragment> | ||||
| 
 | ||||
|     <CollectionsExportForm /> | ||||
| </OverlayPanel> | ||||
|  | @ -13,7 +13,6 @@ | |||
|     import CollectionsSidebar from "@/components/collections/CollectionsSidebar.svelte"; | ||||
|     import CollectionUpsertPanel from "@/components/collections/CollectionUpsertPanel.svelte"; | ||||
|     import CollectionDocsPanel from "@/components/collections/docs/CollectionDocsPanel.svelte"; | ||||
|     import CollectionsExportPanel from "@/components/collections/CollectionsExportPanel.svelte"; | ||||
|     import RecordUpsertPanel from "@/components/records/RecordUpsertPanel.svelte"; | ||||
|     import RecordsList from "@/components/records/RecordsList.svelte"; | ||||
| 
 | ||||
|  | @ -21,7 +20,6 @@ | |||
| 
 | ||||
|     const queryParams = new URLSearchParams($querystring); | ||||
| 
 | ||||
|     let collectionsExportPanel; | ||||
|     let collectionUpsertPanel; | ||||
|     let collectionDocsPanel; | ||||
|     let recordPanel; | ||||
|  | @ -132,8 +130,6 @@ | |||
|     </main> | ||||
| {/if} | ||||
| 
 | ||||
| <CollectionsExportPanel bind:this={collectionsExportPanel} /> | ||||
| 
 | ||||
| <CollectionUpsertPanel bind:this={collectionUpsertPanel} /> | ||||
| 
 | ||||
| <CollectionDocsPanel bind:this={collectionDocsPanel} /> | ||||
|  |  | |||
|  | @ -1,17 +1,19 @@ | |||
| <script> | ||||
|     import { createEventDispatcher } from "svelte"; | ||||
|     import ApiClient from "@/utils/ApiClient"; | ||||
|     import OverlayPanel from "@/components/base/OverlayPanel.svelte"; | ||||
|     import { addSuccessToast } from "@/stores/toasts"; | ||||
| 
 | ||||
|     export let title = "Side-by-side diff"; | ||||
|     export let contentATitle = "Old state"; | ||||
|     export let contentBTitle = "New state"; | ||||
|     const dispatch = createEventDispatcher(); | ||||
| 
 | ||||
|     let panel; | ||||
|     let contentA = ""; | ||||
|     let contentB = ""; | ||||
|     let oldCollections = []; | ||||
|     let newCollections = []; | ||||
|     let isImporting = false; | ||||
| 
 | ||||
|     export function show(a, b) { | ||||
|         contentA = a; | ||||
|         contentB = b; | ||||
|         oldCollections = a; | ||||
|         newCollections = b; | ||||
| 
 | ||||
|         panel?.show(); | ||||
|     } | ||||
|  | @ -20,7 +22,7 @@ | |||
|         return panel?.hide(); | ||||
|     } | ||||
| 
 | ||||
|     function diffsToHtml(diffs, ops = [DIFF_INSERT, DIFF_DELETE, DIFF_EQUAL]) { | ||||
|     function diffsToHtml(diffs, ops = [window.DIFF_INSERT, window.DIFF_DELETE, window.DIFF_EQUAL]) { | ||||
|         const html = []; | ||||
|         const pattern_amp = /&/g; | ||||
|         const pattern_lt = /</g; | ||||
|  | @ -56,35 +58,66 @@ | |||
|         return html.join(""); | ||||
|     } | ||||
| 
 | ||||
|     function diff(ops = [DIFF_INSERT, DIFF_DELETE, DIFF_EQUAL]) { | ||||
|     function diff(ops = [window.DIFF_INSERT, window.DIFF_DELETE, window.DIFF_EQUAL]) { | ||||
|         const dmp = new diff_match_patch(); | ||||
|         const lines = dmp.diff_linesToChars_(contentA, contentB); | ||||
|         const lines = dmp.diff_linesToChars_( | ||||
|             JSON.stringify(oldCollections, null, 4), | ||||
|             JSON.stringify(newCollections, null, 4) | ||||
|         ); | ||||
|         const diffs = dmp.diff_main(lines.chars1, lines.chars2, false); | ||||
| 
 | ||||
|         dmp.diff_charsToLines_(diffs, lines.lineArray); | ||||
| 
 | ||||
|         return diffsToHtml(diffs, ops); | ||||
|     } | ||||
| 
 | ||||
|     async function submitImport() { | ||||
|         if (isImporting) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         isImporting = true; | ||||
| 
 | ||||
|         try { | ||||
|             await ApiClient.collections.import(newCollections); | ||||
|             addSuccessToast("Successfully imported the provided schema."); | ||||
|             dispatch("submit"); | ||||
|         } catch (err) { | ||||
|             ApiClient.errorResponseHandler(err); | ||||
|         } | ||||
| 
 | ||||
|         hide(); | ||||
| 
 | ||||
|         isImporting = false; | ||||
|     } | ||||
| </script> | ||||
| 
 | ||||
| <OverlayPanel bind:this={panel} class="full-width-popup diff-popup" popup on:show on:hide> | ||||
| <OverlayPanel bind:this={panel} class="full-width-popup import-popup" popup on:show on:hide> | ||||
|     <svelte:fragment slot="header"> | ||||
|         <h4 class="center txt-break">{title}</h4> | ||||
|         <h4 class="center txt-break">Side-by-side diff</h4> | ||||
|     </svelte:fragment> | ||||
| 
 | ||||
|     <div class="grid m-b-base"> | ||||
|         <div class="col-6"> | ||||
|             <div class="section-title">{contentATitle}</div> | ||||
|             <code class="code-block">{@html diff([DIFF_DELETE, DIFF_EQUAL])}</code> | ||||
|             <div class="section-title">Old schema</div> | ||||
|             <code class="code-block">{@html diff([window.DIFF_DELETE, window.DIFF_EQUAL])}</code> | ||||
|         </div> | ||||
|         <div class="col-6"> | ||||
|             <div class="section-title">{contentBTitle}</div> | ||||
|             <code class="code-block">{@html diff([DIFF_INSERT, DIFF_EQUAL])}</code> | ||||
|             <div class="section-title">New schema</div> | ||||
|             <code class="code-block">{@html diff([window.DIFF_INSERT, window.DIFF_EQUAL])}</code> | ||||
|         </div> | ||||
|     </div> | ||||
| 
 | ||||
|     <svelte:fragment slot="footer"> | ||||
|         <button type="button" class="btn btn-secondary" on:click={hide}>Close</button> | ||||
|         <button | ||||
|             type="button" | ||||
|             class="btn btn-expanded m-l-auto" | ||||
|             class:btn-loading={isImporting} | ||||
|             on:click={() => submitImport()} | ||||
|         > | ||||
|             <span class="txt">Confirm and import</span> | ||||
|         </button> | ||||
|     </svelte:fragment> | ||||
| </OverlayPanel> | ||||
| 
 | ||||
|  | @ -3,18 +3,18 @@ | |||
|     import ApiClient from "@/utils/ApiClient"; | ||||
|     import CommonHelper from "@/utils/CommonHelper"; | ||||
|     import { pageTitle } from "@/stores/app"; | ||||
|     import { addInfoToast, addErrorToast } from "@/stores/toasts"; | ||||
|     import { addErrorToast } from "@/stores/toasts"; | ||||
|     import { setErrors } from "@/stores/errors"; | ||||
|     import Field from "@/components/base/Field.svelte"; | ||||
|     import DiffPopup from "@/components/base/DiffPopup.svelte"; | ||||
|     import SettingsSidebar from "@/components/settings/SettingsSidebar.svelte"; | ||||
|     import ImportPopup from "@/components/settings/ImportPopup.svelte"; | ||||
| 
 | ||||
|     $pageTitle = "Import collections"; | ||||
| 
 | ||||
|     let fileInput; | ||||
|     let diffPopup; | ||||
|     let importPopup; | ||||
| 
 | ||||
|     let schema = ""; | ||||
|     let isImporting = false; | ||||
|     let isLoadingFile = false; | ||||
|     let newCollections = []; | ||||
|     let oldCollections = []; | ||||
|  | @ -141,22 +141,10 @@ | |||
|         reader.readAsText(file); | ||||
|     } | ||||
| 
 | ||||
|     function submitImport() { | ||||
|         isImporting = true; | ||||
| 
 | ||||
|         try { | ||||
|             const newCollections = JSON.parse(schema); | ||||
|             ApiClient.collections.import(newCollections); | ||||
|         } catch (err) { | ||||
|             ApiClient.errorResponseHandler(err); | ||||
|         } | ||||
| 
 | ||||
|         isImporting = false; | ||||
|     } | ||||
| 
 | ||||
|     function clear() { | ||||
|         schema = ""; | ||||
|         fileInput.value = ""; | ||||
|         setErrors({}); | ||||
|     } | ||||
| </script> | ||||
| 
 | ||||
|  | @ -224,36 +212,23 @@ | |||
|                             <i class="ri-information-line" /> | ||||
|                         </div> | ||||
|                         <div class="content"> | ||||
|                             <string>Everything is up-to-date!</string> | ||||
|                             <string>Your collections schema is already up-to-date!</string> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 {/if} | ||||
| 
 | ||||
|                 {#if isValid && newCollections.length && hasChanges} | ||||
|                     <div class="flex flex-gap-10"> | ||||
|                         <div> | ||||
|                             <h5 class="section-title m-0">Detected changes</h5> | ||||
|                         </div> | ||||
|                         <button | ||||
|                             type="button" | ||||
|                             class="btn btn-sm btn-warning" | ||||
|                             on:click={() => { | ||||
|                                 diffPopup?.show( | ||||
|                                     JSON.stringify(oldCollections, null, 2), | ||||
|                                     JSON.stringify(newCollections, null, 2) | ||||
|                                 ); | ||||
|                             }} | ||||
|                         > | ||||
|                             View diff | ||||
|                         </button> | ||||
|                     </div> | ||||
|                     <h5 class="section-title">Detected changes</h5> | ||||
| 
 | ||||
|                     <div class="list m-t-sm"> | ||||
|                     <div class="list"> | ||||
|                         {#if collectionsToDelete.length} | ||||
|                             {#each collectionsToDelete as collection (collection.id)} | ||||
|                                 <div class="list-item"> | ||||
|                                     <span class="label label-danger list-label">Deleted</span> | ||||
|                                     <strong>{collection.name}</strong> | ||||
|                                     {#if collection.id} | ||||
|                                         <small class="txt-hint">({collection.id})</small> | ||||
|                                     {/if} | ||||
|                                 </div> | ||||
|                             {/each} | ||||
|                         {/if} | ||||
|  | @ -268,6 +243,9 @@ | |||
|                                         {/if} | ||||
|                                         {entry.new.name} | ||||
|                                     </strong> | ||||
|                                     {#if entry.new.id} | ||||
|                                         <small class="txt-hint">({entry.new.id})</small> | ||||
|                                     {/if} | ||||
|                                 </div> | ||||
|                             {/each} | ||||
|                         {/if} | ||||
|  | @ -277,6 +255,9 @@ | |||
|                                 <div class="list-item"> | ||||
|                                     <span class="label label-success list-label">New</span> | ||||
|                                     <strong>{collection.name}</strong> | ||||
|                                     {#if collection.id} | ||||
|                                         <small class="txt-hint">({collection.id})</small> | ||||
|                                     {/if} | ||||
|                                 </div> | ||||
|                             {/each} | ||||
|                         {/if} | ||||
|  | @ -284,23 +265,19 @@ | |||
|                 {/if} | ||||
| 
 | ||||
|                 <div class="flex m-t-base"> | ||||
|                     <button | ||||
|                         type="button" | ||||
|                         class="btn btn-secondary link-hint" | ||||
|                         disabled={!schema || isImporting} | ||||
|                         on:click={() => clear()} | ||||
|                     > | ||||
|                         <span class="txt">Clear</span> | ||||
|                     </button> | ||||
|                     {#if !!schema} | ||||
|                         <button type="button" class="btn btn-secondary link-hint" on:click={() => clear()}> | ||||
|                             <span class="txt">Clear</span> | ||||
|                         </button> | ||||
|                     {/if} | ||||
|                     <div class="flex-fill" /> | ||||
|                     <button | ||||
|                         type="button" | ||||
|                         class="btn btn-expanded m-l-auto" | ||||
|                         class:btn-loading={isImporting} | ||||
|                         class="btn btn-expanded btn-warning m-l-auto" | ||||
|                         disabled={!canImport} | ||||
|                         on:click={() => submitImport()} | ||||
|                         on:click={() => importPopup?.show(oldCollections, newCollections)} | ||||
|                     > | ||||
|                         <span class="txt">Import</span> | ||||
|                         <span class="txt">Review</span> | ||||
|                     </button> | ||||
|                 </div> | ||||
|             {/if} | ||||
|  | @ -308,7 +285,7 @@ | |||
|     </div> | ||||
| </main> | ||||
| 
 | ||||
| <DiffPopup bind:this={diffPopup} /> | ||||
| <ImportPopup bind:this={importPopup} on:submit={() => clear()} /> | ||||
| 
 | ||||
| <style> | ||||
|     .list-label { | ||||
|  |  | |||
|  | @ -667,7 +667,7 @@ a, | |||
|     border: 1px solid var(--baseAlt1Color); | ||||
|     border-radius: var(--baseRadius); | ||||
|     .list-item { | ||||
|         word-break: break-all; | ||||
|         word-break: break-word; | ||||
|         position: relative; | ||||
|         display: flex; | ||||
|         align-items: center; | ||||
|  |  | |||
|  | @ -495,6 +495,7 @@ select { | |||
|         font-size: var(--smFontSize); | ||||
|         line-height: var(--smLineHeight); | ||||
|         color: var(--txtHintColor); | ||||
|         word-break: break-word; | ||||
|     } | ||||
|     .help-block-error { | ||||
|         color: var(--dangerColor); | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue