@girder/core 5.0.0-beta.1 → 5.0.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/dist-lib/Girder_Favicon.png +0 -0
- package/dist-lib/Girder_Mark.png +0 -0
- package/dist-lib/girder-core.css +2 -0
- package/dist-lib/girder-core.js +60421 -0
- package/dist-lib/girder-core.js.map +1 -0
- package/dist-lib/girder-core.umd.cjs +1484 -0
- package/dist-lib/girder-core.umd.cjs.map +1 -0
- package/dist-lib/index.d.ts +1 -0
- package/dist-lib/src/auth.js +112 -0
- package/dist-lib/src/collections/ApiKeyCollection.js +9 -0
- package/dist-lib/src/collections/AssetstoreCollection.js +10 -0
- package/dist-lib/src/collections/Collection.js +295 -0
- package/dist-lib/src/collections/CollectionCollection.js +9 -0
- package/dist-lib/src/collections/FileCollection.js +11 -0
- package/dist-lib/src/collections/FolderCollection.js +11 -0
- package/dist-lib/src/collections/GroupCollection.js +9 -0
- package/dist-lib/src/collections/ItemCollection.js +11 -0
- package/dist-lib/src/collections/UserCollection.js +24 -0
- package/dist-lib/src/collections/index.js +21 -0
- package/dist-lib/src/constants.js +32 -0
- package/dist-lib/src/dialog.js +128 -0
- package/dist-lib/src/events.js +6 -0
- package/dist-lib/src/index.ts +80 -0
- package/dist-lib/src/main.ts +61 -0
- package/dist-lib/src/misc.js +260 -0
- package/dist-lib/src/models/AccessControlledModel.js +76 -0
- package/dist-lib/src/models/ApiKeyModel.js +31 -0
- package/dist-lib/src/models/AssetstoreModel.js +43 -0
- package/dist-lib/src/models/CollectionCreationPolicyModel.js +20 -0
- package/dist-lib/src/models/CollectionModel.js +12 -0
- package/dist-lib/src/models/FileModel.js +310 -0
- package/dist-lib/src/models/FolderModel.js +33 -0
- package/dist-lib/src/models/GroupModel.js +197 -0
- package/dist-lib/src/models/ItemModel.js +72 -0
- package/dist-lib/src/models/MetadataMixin.js +88 -0
- package/dist-lib/src/models/Model.js +187 -0
- package/dist-lib/src/models/UserModel.js +189 -0
- package/dist-lib/src/models/index.js +25 -0
- package/dist-lib/src/pluginUtils.js +11 -0
- package/dist-lib/src/rest.js +221 -0
- package/dist-lib/src/router.js +58 -0
- package/dist-lib/src/routes.js +229 -0
- package/dist-lib/src/stylesheets/apidocs/apidocs.styl +50 -0
- package/dist-lib/src/stylesheets/body/adminConsole.styl +21 -0
- package/dist-lib/src/stylesheets/body/assetstores.styl +46 -0
- package/dist-lib/src/stylesheets/body/collectionList.styl +39 -0
- package/dist-lib/src/stylesheets/body/collectionPage.styl +6 -0
- package/dist-lib/src/stylesheets/body/frontPage.styl +48 -0
- package/dist-lib/src/stylesheets/body/groupList.styl +43 -0
- package/dist-lib/src/stylesheets/body/groupPage.styl +116 -0
- package/dist-lib/src/stylesheets/body/itemPage.styl +81 -0
- package/dist-lib/src/stylesheets/body/plugins.styl +61 -0
- package/dist-lib/src/stylesheets/body/searchResultsList.styl +51 -0
- package/dist-lib/src/stylesheets/body/systemConfig.styl +56 -0
- package/dist-lib/src/stylesheets/body/userAccount.styl +57 -0
- package/dist-lib/src/stylesheets/body/userList.styl +79 -0
- package/dist-lib/src/stylesheets/body/userPage.styl +6 -0
- package/dist-lib/src/stylesheets/layout/footer.styl +19 -0
- package/dist-lib/src/stylesheets/layout/global.styl +154 -0
- package/dist-lib/src/stylesheets/layout/globalNav.styl +89 -0
- package/dist-lib/src/stylesheets/layout/header.styl +29 -0
- package/dist-lib/src/stylesheets/layout/headerUser.styl +33 -0
- package/dist-lib/src/stylesheets/layout/layout.styl +75 -0
- package/dist-lib/src/stylesheets/layout/layoutVars.styl +9 -0
- package/dist-lib/src/stylesheets/layout/loading.styl +37 -0
- package/dist-lib/src/stylesheets/layout/progressArea.styl +17 -0
- package/dist-lib/src/stylesheets/widgets/accessWidget.styl +106 -0
- package/dist-lib/src/stylesheets/widgets/browserWidget.styl +9 -0
- package/dist-lib/src/stylesheets/widgets/hierarchyWidget.styl +188 -0
- package/dist-lib/src/stylesheets/widgets/markdownWidget.styl +92 -0
- package/dist-lib/src/stylesheets/widgets/metadataWidget.styl +92 -0
- package/dist-lib/src/stylesheets/widgets/searchFieldWidget.styl +70 -0
- package/dist-lib/src/stylesheets/widgets/taskProgress.styl +41 -0
- package/dist-lib/src/stylesheets/widgets/timelineWidget.styl +41 -0
- package/dist-lib/src/stylesheets/widgets/uploadWidget.styl +43 -0
- package/dist-lib/src/stylesheets/widgets/userOtpManagementWidget.styl +159 -0
- package/dist-lib/src/templates/body/adminConsole.pug +13 -0
- package/dist-lib/src/templates/body/assetstores.pug +83 -0
- package/dist-lib/src/templates/body/collectionList.pug +40 -0
- package/dist-lib/src/templates/body/collectionPage.pug +36 -0
- package/dist-lib/src/templates/body/filesystemImport.pug +41 -0
- package/dist-lib/src/templates/body/frontPage.pug +83 -0
- package/dist-lib/src/templates/body/groupList.pug +30 -0
- package/dist-lib/src/templates/body/groupPage.pug +116 -0
- package/dist-lib/src/templates/body/itemPage.pug +61 -0
- package/dist-lib/src/templates/body/plugins.pug +20 -0
- package/dist-lib/src/templates/body/s3Import.pug +35 -0
- package/dist-lib/src/templates/body/searchResults.pug +15 -0
- package/dist-lib/src/templates/body/searchResultsType.pug +13 -0
- package/dist-lib/src/templates/body/systemConfiguration.pug +215 -0
- package/dist-lib/src/templates/body/userAccount.pug +83 -0
- package/dist-lib/src/templates/body/userList.pug +43 -0
- package/dist-lib/src/templates/body/userPage.pug +40 -0
- package/dist-lib/src/templates/layout/alert.pug +5 -0
- package/dist-lib/src/templates/layout/layout.pug +12 -0
- package/dist-lib/src/templates/layout/layoutFooter.pug +11 -0
- package/dist-lib/src/templates/layout/layoutGlobalNav.pug +7 -0
- package/dist-lib/src/templates/layout/layoutHeader.pug +8 -0
- package/dist-lib/src/templates/layout/layoutHeaderUser.pug +26 -0
- package/dist-lib/src/templates/layout/layoutProgressArea.pug +1 -0
- package/dist-lib/src/templates/layout/loginDialog.pug +30 -0
- package/dist-lib/src/templates/layout/registerDialog.pug +35 -0
- package/dist-lib/src/templates/layout/resetPasswordDialog.pug +25 -0
- package/dist-lib/src/templates/widgets/accessEditor.pug +23 -0
- package/dist-lib/src/templates/widgets/accessEditorMixins.pug +57 -0
- package/dist-lib/src/templates/widgets/accessEditorNonModal.pug +11 -0
- package/dist-lib/src/templates/widgets/accessEntry.pug +32 -0
- package/dist-lib/src/templates/widgets/apiKeyList.pug +50 -0
- package/dist-lib/src/templates/widgets/browserWidget.pug +32 -0
- package/dist-lib/src/templates/widgets/checkedActionsMenu.pug +46 -0
- package/dist-lib/src/templates/widgets/collectionInfoDialog.pug +37 -0
- package/dist-lib/src/templates/widgets/confirmDialog.pug +14 -0
- package/dist-lib/src/templates/widgets/dateTimeRangeWidget.pug +20 -0
- package/dist-lib/src/templates/widgets/dateTimeWidget.pug +8 -0
- package/dist-lib/src/templates/widgets/editApiKeyWidget.pug +43 -0
- package/dist-lib/src/templates/widgets/editAssetstoreWidget.pug +70 -0
- package/dist-lib/src/templates/widgets/editCollectionWidget.pug +27 -0
- package/dist-lib/src/templates/widgets/editFileWidget.pug +21 -0
- package/dist-lib/src/templates/widgets/editFolderWidget.pug +27 -0
- package/dist-lib/src/templates/widgets/editGroupWidget.pug +54 -0
- package/dist-lib/src/templates/widgets/editItemWidget.pug +27 -0
- package/dist-lib/src/templates/widgets/fileInfoDialog.pug +33 -0
- package/dist-lib/src/templates/widgets/fileList.pug +33 -0
- package/dist-lib/src/templates/widgets/folderInfoDialog.pug +42 -0
- package/dist-lib/src/templates/widgets/folderList.pug +21 -0
- package/dist-lib/src/templates/widgets/groupAdminList.pug +33 -0
- package/dist-lib/src/templates/widgets/groupInviteDialog.pug +76 -0
- package/dist-lib/src/templates/widgets/groupInviteList.pug +14 -0
- package/dist-lib/src/templates/widgets/groupMemberList.pug +39 -0
- package/dist-lib/src/templates/widgets/groupModList.pug +23 -0
- package/dist-lib/src/templates/widgets/hierarchyBreadcrumb.pug +37 -0
- package/dist-lib/src/templates/widgets/hierarchyPaginated.pug +19 -0
- package/dist-lib/src/templates/widgets/hierarchyWidget.pug +96 -0
- package/dist-lib/src/templates/widgets/itemBreadcrumb.pug +16 -0
- package/dist-lib/src/templates/widgets/itemList.pug +27 -0
- package/dist-lib/src/templates/widgets/jsonMetadatumEditWidget.pug +13 -0
- package/dist-lib/src/templates/widgets/jsonMetadatumView.pug +6 -0
- package/dist-lib/src/templates/widgets/loadingAnimation.pug +4 -0
- package/dist-lib/src/templates/widgets/markdownWidget.pug +34 -0
- package/dist-lib/src/templates/widgets/metadataWidget.pug +16 -0
- package/dist-lib/src/templates/widgets/metadatumEditWidget.pug +15 -0
- package/dist-lib/src/templates/widgets/metadatumView.pug +5 -0
- package/dist-lib/src/templates/widgets/newAssetstore.pug +87 -0
- package/dist-lib/src/templates/widgets/paginateWidget.pug +7 -0
- package/dist-lib/src/templates/widgets/pluginConfigBreadcrumb.pug +13 -0
- package/dist-lib/src/templates/widgets/rootSelectorWidget.pug +13 -0
- package/dist-lib/src/templates/widgets/searchField.pug +10 -0
- package/dist-lib/src/templates/widgets/searchHelp.pug +12 -0
- package/dist-lib/src/templates/widgets/searchModeSelect.pug +9 -0
- package/dist-lib/src/templates/widgets/searchResults.pug +14 -0
- package/dist-lib/src/templates/widgets/sortCollectionWidget.pug +14 -0
- package/dist-lib/src/templates/widgets/taskProgress.pug +16 -0
- package/dist-lib/src/templates/widgets/timeline.pug +15 -0
- package/dist-lib/src/templates/widgets/uploadWidget.pug +15 -0
- package/dist-lib/src/templates/widgets/uploadWidgetMixins.pug +31 -0
- package/dist-lib/src/templates/widgets/uploadWidgetNonModal.pug +10 -0
- package/dist-lib/src/templates/widgets/userOtpBegin.pug +4 -0
- package/dist-lib/src/templates/widgets/userOtpDisable.pug +6 -0
- package/dist-lib/src/templates/widgets/userOtpEnable.pug +44 -0
- package/dist-lib/src/utilities/EventStream.js +119 -0
- package/dist-lib/src/utilities/PluginUtils.js +36 -0
- package/dist-lib/src/utilities/S3UploadHandler.js +263 -0
- package/dist-lib/src/utilities/index.js +9 -0
- package/dist-lib/src/utilities/jquery/girderEnable.js +19 -0
- package/dist-lib/src/utilities/jquery/girderModal.js +48 -0
- package/dist-lib/src/version.js +6 -0
- package/dist-lib/src/views/App.js +359 -0
- package/dist-lib/src/views/View.js +79 -0
- package/dist-lib/src/views/body/AdminView.js +29 -0
- package/dist-lib/src/views/body/AssetstoresView.js +233 -0
- package/dist-lib/src/views/body/CollectionView.js +188 -0
- package/dist-lib/src/views/body/CollectionsView.js +120 -0
- package/dist-lib/src/views/body/FilesystemImportView.js +83 -0
- package/dist-lib/src/views/body/FolderView.js +54 -0
- package/dist-lib/src/views/body/FrontPageView.js +47 -0
- package/dist-lib/src/views/body/GroupView.js +336 -0
- package/dist-lib/src/views/body/GroupsView.js +106 -0
- package/dist-lib/src/views/body/ItemView.js +177 -0
- package/dist-lib/src/views/body/PluginsView.js +73 -0
- package/dist-lib/src/views/body/S3ImportView.js +80 -0
- package/dist-lib/src/views/body/SearchResultsView.js +162 -0
- package/dist-lib/src/views/body/SystemConfigurationView.js +177 -0
- package/dist-lib/src/views/body/UserAccountView.js +179 -0
- package/dist-lib/src/views/body/UserView.js +165 -0
- package/dist-lib/src/views/body/UsersView.js +124 -0
- package/dist-lib/src/views/body/index.js +38 -0
- package/dist-lib/src/views/index.js +13 -0
- package/dist-lib/src/views/layout/FooterView.js +29 -0
- package/dist-lib/src/views/layout/GlobalNavView.js +103 -0
- package/dist-lib/src/views/layout/HeaderUserView.js +45 -0
- package/dist-lib/src/views/layout/HeaderView.js +83 -0
- package/dist-lib/src/views/layout/LoginView.js +100 -0
- package/dist-lib/src/views/layout/ProgressListView.js +70 -0
- package/dist-lib/src/views/layout/RegisterView.js +101 -0
- package/dist-lib/src/views/layout/ResetPasswordView.js +70 -0
- package/dist-lib/src/views/layout/index.js +19 -0
- package/dist-lib/src/views/widgets/AccessWidget.js +427 -0
- package/dist-lib/src/views/widgets/ApiKeyListWidget.js +140 -0
- package/dist-lib/src/views/widgets/BrowserWidget.js +317 -0
- package/dist-lib/src/views/widgets/CheckedMenuWidget.js +68 -0
- package/dist-lib/src/views/widgets/CollectionInfoWidget.js +40 -0
- package/dist-lib/src/views/widgets/DateTimeRangeWidget.js +180 -0
- package/dist-lib/src/views/widgets/DateTimeWidget.js +110 -0
- package/dist-lib/src/views/widgets/EditApiKeyWidget.js +124 -0
- package/dist-lib/src/views/widgets/EditAssetstoreWidget.js +136 -0
- package/dist-lib/src/views/widgets/EditCollectionWidget.js +93 -0
- package/dist-lib/src/views/widgets/EditFileWidget.js +56 -0
- package/dist-lib/src/views/widgets/EditFolderWidget.js +116 -0
- package/dist-lib/src/views/widgets/EditGroupWidget.js +125 -0
- package/dist-lib/src/views/widgets/EditItemWidget.js +110 -0
- package/dist-lib/src/views/widgets/FileInfoWidget.js +26 -0
- package/dist-lib/src/views/widgets/FileListWidget.js +151 -0
- package/dist-lib/src/views/widgets/FolderInfoWidget.js +39 -0
- package/dist-lib/src/views/widgets/FolderListWidget.js +106 -0
- package/dist-lib/src/views/widgets/GroupAdminsWidget.js +88 -0
- package/dist-lib/src/views/widgets/GroupInvitesWidget.js +56 -0
- package/dist-lib/src/views/widgets/GroupMembersWidget.js +185 -0
- package/dist-lib/src/views/widgets/GroupModsWidget.js +77 -0
- package/dist-lib/src/views/widgets/HierarchyWidget.js +1097 -0
- package/dist-lib/src/views/widgets/ItemBreadcrumbWidget.js +38 -0
- package/dist-lib/src/views/widgets/ItemListWidget.js +345 -0
- package/dist-lib/src/views/widgets/LoadingAnimation.js +17 -0
- package/dist-lib/src/views/widgets/MarkdownWidget.js +217 -0
- package/dist-lib/src/views/widgets/MetadataWidget.js +508 -0
- package/dist-lib/src/views/widgets/NewAssetstoreWidget.js +72 -0
- package/dist-lib/src/views/widgets/PaginateWidget.js +37 -0
- package/dist-lib/src/views/widgets/PluginConfigBreadcrumbWidget.js +33 -0
- package/dist-lib/src/views/widgets/RootSelectorWidget.js +192 -0
- package/dist-lib/src/views/widgets/SearchFieldWidget.js +365 -0
- package/dist-lib/src/views/widgets/SearchPaginateWidget.js +149 -0
- package/dist-lib/src/views/widgets/SortCollectionWidget.js +53 -0
- package/dist-lib/src/views/widgets/TaskProgressWidget.js +91 -0
- package/dist-lib/src/views/widgets/TimelineWidget.js +155 -0
- package/dist-lib/src/views/widgets/UploadWidget.js +340 -0
- package/dist-lib/src/views/widgets/UserOtpManagementWidget.js +105 -0
- package/dist-lib/src/views/widgets/index.js +81 -0
- package/dist-lib/src/vite-env.d.ts +5 -0
- package/package.json +17 -15
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
each result in results
|
|
2
|
+
li.g-search-result
|
|
3
|
+
a.g-search-result-element(data-resource-id=result.id, data-resource-type=result.type, data-resource-icon=result.icon, tabindex="-1")
|
|
4
|
+
i(class=`icon-${result.icon}`)
|
|
5
|
+
| #{result.text}
|
|
6
|
+
if results.length === 0
|
|
7
|
+
li.g-no-search-results.disabled
|
|
8
|
+
a(tabindex="-1")
|
|
9
|
+
i.icon-block
|
|
10
|
+
| No results found
|
|
11
|
+
else
|
|
12
|
+
li.g-search-result
|
|
13
|
+
a.g-search-result-page-link(data-resource-type="resultPage")
|
|
14
|
+
| ...
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
.btn-group
|
|
2
|
+
a.g-sort-order-button.g-up.btn.btn-sm(title="Sort order")
|
|
3
|
+
i.icon-sort-name-up
|
|
4
|
+
a.g-sort-order-button.g-down.btn.btn-sm(title="Sort order")
|
|
5
|
+
i.icon-sort-name-down
|
|
6
|
+
button.g-collection-sort-actions.btn.btn-sm.btn-default.dropdown-toggle(
|
|
7
|
+
data-toggle="dropdown", title="Sort by Field")
|
|
8
|
+
| Sort by
|
|
9
|
+
i.icon-down-dir
|
|
10
|
+
ul.g-collection-sort-menu.dropdown-menu(role="menu")
|
|
11
|
+
each fieldName, field in fields
|
|
12
|
+
li(role="presentation")
|
|
13
|
+
a.g-collection-sort-link(role="menuitem", g-sort=field)
|
|
14
|
+
= fieldName
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
.g-task-progress-top
|
|
2
|
+
.g-task-progress-bar.progress(class=progressClass)
|
|
3
|
+
.progress-bar(class=barClass, style=`width: ${width};`)
|
|
4
|
+
.progress-status
|
|
5
|
+
.progress-percent
|
|
6
|
+
= percentText
|
|
7
|
+
.progress-left
|
|
8
|
+
= timeLeftText
|
|
9
|
+
.g-task-progress-title(title=progress.data.title)
|
|
10
|
+
if progress.data.resource && progress.data.resource._id && progress.data.resourceName
|
|
11
|
+
a(title=progress.data.title, href='#' + progress.data.resourceName + '/' + progress.data.resource._id)
|
|
12
|
+
= progress.data.title
|
|
13
|
+
else
|
|
14
|
+
= progress.data.title
|
|
15
|
+
.g-task-progress-bottom
|
|
16
|
+
.g-task-progress-message= progress.data.message
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
.g-timeline-widget-wrapper
|
|
2
|
+
.g-timeline-container
|
|
3
|
+
each segment in segments
|
|
4
|
+
.g-timeline-segment(
|
|
5
|
+
title=segment.tooltip, class=segment.class,
|
|
6
|
+
style=`left: ${segment.left}; width: ${segment.width}; ${segment.color}`)
|
|
7
|
+
each point in points
|
|
8
|
+
.g-timeline-point(
|
|
9
|
+
title=point.tooltip, class=point.class, style=`left: calc(${point.left} - 10px); ${point.color}`)
|
|
10
|
+
if startLabel !== undefined || endLabel !== undefined
|
|
11
|
+
.g-timeline-labels-container
|
|
12
|
+
if startLabel !== undefined
|
|
13
|
+
.g-timeline-start-label= startLabel
|
|
14
|
+
if endLabel !== undefined
|
|
15
|
+
.g-timeline-end-label= endLabel
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
include uploadWidgetMixins
|
|
2
|
+
|
|
3
|
+
.modal-dialog
|
|
4
|
+
.modal-content
|
|
5
|
+
form#g-upload-form.modal-form(role="form")
|
|
6
|
+
.modal-header
|
|
7
|
+
button.close(data-dismiss="modal", aria-hidden="true", type="button") ×
|
|
8
|
+
h4.modal-title= title
|
|
9
|
+
+g-upload-widget-subtitle
|
|
10
|
+
.modal-body
|
|
11
|
+
+g-upload-widget-browse-or-drop
|
|
12
|
+
+g-upload-widget-progress
|
|
13
|
+
.modal-footer
|
|
14
|
+
a.btn.btn-small.btn-default(data-dismiss="modal") Close
|
|
15
|
+
+g-upload-widget-start-button
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
mixin g-upload-widget-subtitle
|
|
2
|
+
if parent && parentType
|
|
3
|
+
.g-dialog-subtitle
|
|
4
|
+
if (parentType === 'folder')
|
|
5
|
+
i.icon-folder-open
|
|
6
|
+
else if (parentType === 'item')
|
|
7
|
+
i.icon-doc-text-inv
|
|
8
|
+
else if (parentType === 'file')
|
|
9
|
+
i.icon-doc-inv
|
|
10
|
+
= parent.name()
|
|
11
|
+
|
|
12
|
+
mixin g-upload-widget-browse-or-drop
|
|
13
|
+
.g-drop-zone
|
|
14
|
+
i.icon-docs
|
|
15
|
+
| #{browseText}
|
|
16
|
+
.form-group.hide
|
|
17
|
+
input#g-files(type="file", multiple=(multiFile ? 'multiple' : null))
|
|
18
|
+
|
|
19
|
+
mixin g-upload-widget-progress
|
|
20
|
+
.g-current-progress-message
|
|
21
|
+
.g-progress-current.progress.progress-striped.hide
|
|
22
|
+
.progress-bar.progress-bar-info(role="progressbar")
|
|
23
|
+
.g-overall-progress-message= noneSelectedText
|
|
24
|
+
.g-progress-overall.progress.progress-striped.hide
|
|
25
|
+
.progress-bar.progress-bar-info(role="progressbar")
|
|
26
|
+
.g-upload-error-message.g-validation-failed-message
|
|
27
|
+
|
|
28
|
+
mixin g-upload-widget-start-button
|
|
29
|
+
button.g-start-upload.btn.btn-small.btn-primary.disabled(type="submit", disabled)
|
|
30
|
+
i.icon-upload
|
|
31
|
+
| Start Upload
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
h2 Enable Two-factor authentication
|
|
2
|
+
|
|
3
|
+
ol.g-account-otp-steps
|
|
4
|
+
li.g-account-otp-step
|
|
5
|
+
h3 Install an authenticator app on your mobile device.
|
|
6
|
+
div.
|
|
7
|
+
There are many compatible apps, including, but not limited to
|
|
8
|
+
#[a(href='https://support.google.com/accounts/answer/1066447?hl=en', target='_blank', rel='noopener') Google Authenticator],
|
|
9
|
+
#[a(href='https://guide.duo.com/third-party-accounts', target='_blank', rel='noopener') Duo Mobile], and
|
|
10
|
+
#[a(href='https://freeotp.github.io/', target='_blank', rel='noopener') FreeOTP].
|
|
11
|
+
li.g-account-otp-step
|
|
12
|
+
h3 Enter your key into the authenticator app
|
|
13
|
+
.g-account-otp-scan-qr
|
|
14
|
+
h4 Scan with your mobile device's camera:
|
|
15
|
+
canvas#g-user-otp-qr
|
|
16
|
+
.g-account-otp-enter-manual
|
|
17
|
+
h4 Advanced Users:
|
|
18
|
+
span You may enter your information manually, instead:
|
|
19
|
+
table
|
|
20
|
+
tr
|
|
21
|
+
th Service
|
|
22
|
+
td
|
|
23
|
+
code= totpInfo.issuer
|
|
24
|
+
tr
|
|
25
|
+
th Account
|
|
26
|
+
td
|
|
27
|
+
code= totpInfo.account
|
|
28
|
+
tr
|
|
29
|
+
th Key
|
|
30
|
+
td
|
|
31
|
+
// Split into 4-character chunks for human readability
|
|
32
|
+
code= totpInfo.key.match(/.{1,4}/g).join('-')
|
|
33
|
+
tr
|
|
34
|
+
th Type
|
|
35
|
+
td
|
|
36
|
+
span #[code Time-based] / #[code TOTP]
|
|
37
|
+
li.g-account-otp-step
|
|
38
|
+
h3 Enter the authentication code from your app
|
|
39
|
+
input#g-user-otp-token(type='text', minlength=6, maxlength=6, placeholder='Your 6-digit code')
|
|
40
|
+
div
|
|
41
|
+
span#g-account-otp-error.g-validation-failed-message
|
|
42
|
+
.g-user-otp-footer
|
|
43
|
+
a#g-user-otp-cancel-enable.btn.btn-default Cancel
|
|
44
|
+
button#g-user-otp-finalize-enable.btn.btn-primary.btn-success(type="submit") Enable Two-Factor
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import _ from 'underscore';
|
|
2
|
+
import Backbone from 'backbone';
|
|
3
|
+
|
|
4
|
+
import { getCurrentToken } from '@girder/core/auth';
|
|
5
|
+
|
|
6
|
+
const appElement = document.getElementById('app');
|
|
7
|
+
let apiRoot = (appElement && appElement.getAttribute('root')) || '';
|
|
8
|
+
apiRoot += (apiRoot.endsWith('/') ? '' : '/') + 'api/v1';
|
|
9
|
+
if (!apiRoot.startsWith('/') && apiRoot.indexOf(':') < 0) {
|
|
10
|
+
apiRoot = '/' + apiRoot;
|
|
11
|
+
}
|
|
12
|
+
let notifyRoot = apiRoot.endsWith('/api/v1') ? apiRoot.slice(0, -6) : apiRoot;
|
|
13
|
+
notifyRoot = notifyRoot.endsWith('/') ? notifyRoot.slice(0, -1) : notifyRoot;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The EventStream is an abstraction for server-sent events / long-polling via a
|
|
17
|
+
* per-user event channel endpoint using a WebSocket. When events are
|
|
18
|
+
* received on the websocket channel, this triggers a Backbone event of the form
|
|
19
|
+
* 'g:event.<type>' where <type> is the value of the event type field.
|
|
20
|
+
* Listeners can bind to specific event types on the channel.
|
|
21
|
+
*/
|
|
22
|
+
function EventStream(settings) {
|
|
23
|
+
// Possible states are 'closed', 'stopped', 'started'
|
|
24
|
+
this._state = 'closed';
|
|
25
|
+
|
|
26
|
+
// Create context bindings only once, so they can be referenced when unbinding events
|
|
27
|
+
this._onVisibilityStateChange = _.bind(this._onVisibilityStateChange, this);
|
|
28
|
+
this._onMessage = _.bind(this._onMessage, this);
|
|
29
|
+
this._onError = _.bind(this._onError, this);
|
|
30
|
+
|
|
31
|
+
return _.extend(this, Backbone.Events);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
EventStream.prototype._onVisibilityStateChange = function () {
|
|
35
|
+
if (document.visibilityState === 'visible' && this._state === 'stopped') {
|
|
36
|
+
this._start();
|
|
37
|
+
} else if (document.visibilityState === 'hidden' && this._state === 'started') {
|
|
38
|
+
this._stop();
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
EventStream.prototype._onMessage = function (e) {
|
|
43
|
+
let obj;
|
|
44
|
+
try {
|
|
45
|
+
obj = window.JSON.parse(e.data);
|
|
46
|
+
} catch (err) {
|
|
47
|
+
console.error('Invalid JSON from notification stream: ' + e.data + ',' + err);
|
|
48
|
+
this.trigger('g:error', e);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
this.trigger('g:event.' + obj.type, obj);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
EventStream.prototype._onError = function () {
|
|
55
|
+
// TODO: Handle error
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
EventStream.prototype.open = function () {
|
|
59
|
+
if (!window.EventSource) {
|
|
60
|
+
console.error('EventSource is not supported on this platform.');
|
|
61
|
+
this.trigger('g:eventStream.disable');
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (this._state !== 'closed') {
|
|
65
|
+
console.warn('EventStream should be closed.');
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
this._state = 'stopped';
|
|
70
|
+
this._start();
|
|
71
|
+
|
|
72
|
+
document.addEventListener('visibilitychange', this._onVisibilityStateChange);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
EventStream.prototype._start = function () {
|
|
76
|
+
if (this._state !== 'stopped') {
|
|
77
|
+
console.warn('EventStream should be stopped');
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
this._websocket = new WebSocket(`${notifyRoot}/notifications/me?token=${getCurrentToken()}`);
|
|
81
|
+
this._websocket.onmessage = this._onMessage;
|
|
82
|
+
this._websocket.onerror = this._onError;
|
|
83
|
+
|
|
84
|
+
this._state = 'started';
|
|
85
|
+
this.trigger('g:eventStream.start');
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
EventStream.prototype._stop = function () {
|
|
89
|
+
if (this._state !== 'started') {
|
|
90
|
+
console.warn('EventStream should be started');
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
this._websocket.close();
|
|
95
|
+
this._websocket = null;
|
|
96
|
+
|
|
97
|
+
this._state = 'stopped';
|
|
98
|
+
this.trigger('g:eventStream.stop');
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
EventStream.prototype.close = function () {
|
|
102
|
+
if (this._state === 'closed') {
|
|
103
|
+
console.warn('EventStream should not be closed');
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
document.removeEventListener('visibilitychange', this._onVisibilityStateChange);
|
|
108
|
+
|
|
109
|
+
if (this._state === 'started') {
|
|
110
|
+
this._stop();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
this._state = 'closed';
|
|
114
|
+
this.trigger('g:eventStream.close');
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const eventStream = new EventStream();
|
|
118
|
+
|
|
119
|
+
export default eventStream;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import _ from 'underscore';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wrap the prototype method of a given object.
|
|
5
|
+
*
|
|
6
|
+
* @param obj The object whose prototype to extend, e.g. MyView
|
|
7
|
+
* @param funcName The name of the function to wrap, e.g. "render"
|
|
8
|
+
* @param wrapper The wrapper function, which should be a function taking the
|
|
9
|
+
* underlying wrapped function as its first argument. The wrapped function
|
|
10
|
+
* should be called with .call(this[, arguments]) inside of the wrapper in
|
|
11
|
+
* order to preserve "this" semantics.
|
|
12
|
+
*/
|
|
13
|
+
function wrap(obj, funcName, wrapper) {
|
|
14
|
+
obj.prototype[funcName] = _.wrap(obj.prototype[funcName], wrapper);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
var _pluginConfigRoutes = {};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Expose a plugin configuration page via the admin plugins page.
|
|
21
|
+
* @param pluginName The canonical plugin name, i.e. its directory name
|
|
22
|
+
* @param route The route to trigger that will render the plugin config.
|
|
23
|
+
*/
|
|
24
|
+
function exposePluginConfig(pluginName, route) {
|
|
25
|
+
_pluginConfigRoutes[pluginName] = route;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function getPluginConfigRoute(pluginName) {
|
|
29
|
+
return _pluginConfigRoutes[pluginName];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export {
|
|
33
|
+
exposePluginConfig,
|
|
34
|
+
getPluginConfigRoute,
|
|
35
|
+
wrap
|
|
36
|
+
};
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import _ from 'underscore';
|
|
2
|
+
import Backbone from 'backbone';
|
|
3
|
+
|
|
4
|
+
import { restRequest, uploadHandlers } from '@girder/core/rest';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* This is the upload handler for the "s3" behavior, which is responsible for
|
|
8
|
+
* uploading data to an s3 assetstore type directly from the user agent, using
|
|
9
|
+
* either the single-request or multi-chunk protocol depending on the size of
|
|
10
|
+
* the file.
|
|
11
|
+
*
|
|
12
|
+
* The flow here is to make requests to Girder for each required chunk of
|
|
13
|
+
* the upload, which Girder authorizes and signs using HMAC. Those signatures
|
|
14
|
+
* are sent, along with the bytes, to the appropriate S3 bucket. For multi-
|
|
15
|
+
* chunk uploads, one final request is required after all chunks have been
|
|
16
|
+
* sent in order to create the final unified record in S3.
|
|
17
|
+
*/
|
|
18
|
+
// Constructor
|
|
19
|
+
uploadHandlers.s3 = function (params) {
|
|
20
|
+
this.params = params;
|
|
21
|
+
this.startByte = 0;
|
|
22
|
+
return _.extend(this, Backbone.Events);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
var prototype = uploadHandlers.s3.prototype;
|
|
26
|
+
|
|
27
|
+
prototype._xhrProgress = function (event) {
|
|
28
|
+
if (!event.lengthComputable) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// We only want to count bytes of the actual file, not the bytes of
|
|
33
|
+
// the request body corresponding to the other form parameters model
|
|
34
|
+
// we are also sending.
|
|
35
|
+
var loaded = this.payloadLength + event.loaded - event.total;
|
|
36
|
+
|
|
37
|
+
if (loaded >= 0) {
|
|
38
|
+
this.trigger('g:upload.progress', {
|
|
39
|
+
startByte: this.startByte,
|
|
40
|
+
loaded: loaded,
|
|
41
|
+
total: this.params.file.size,
|
|
42
|
+
file: this.params.file
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
prototype.execute = function () {
|
|
48
|
+
var s3Info = this.params.upload.s3;
|
|
49
|
+
|
|
50
|
+
var xhr = new XMLHttpRequest();
|
|
51
|
+
xhr.open(s3Info.request.method, s3Info.request.url);
|
|
52
|
+
_.each(s3Info.request.headers, (v, k) => {
|
|
53
|
+
xhr.setRequestHeader(k, v);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
if (s3Info.chunked) {
|
|
57
|
+
this._multiChunkUpload(xhr);
|
|
58
|
+
} else {
|
|
59
|
+
this.payloadLength = this.params.file.size;
|
|
60
|
+
xhr.onload = () => {
|
|
61
|
+
if (xhr.status === 200) {
|
|
62
|
+
this.trigger('g:upload.chunkSent', {
|
|
63
|
+
bytes: this.payloadLength
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
restRequest({
|
|
67
|
+
url: 'file/completion',
|
|
68
|
+
method: 'POST',
|
|
69
|
+
data: {
|
|
70
|
+
uploadId: this.params.upload._id
|
|
71
|
+
},
|
|
72
|
+
error: null
|
|
73
|
+
}).done((resp) => {
|
|
74
|
+
this.trigger('g:upload.complete', resp);
|
|
75
|
+
}).fail((resp) => {
|
|
76
|
+
var msg;
|
|
77
|
+
|
|
78
|
+
if (resp.status === 0) {
|
|
79
|
+
msg = 'Could not connect to the server.';
|
|
80
|
+
} else {
|
|
81
|
+
msg = 'An error occurred when resuming upload, check console.';
|
|
82
|
+
}
|
|
83
|
+
this.trigger('g:upload.error', {
|
|
84
|
+
message: msg
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
} else {
|
|
88
|
+
this.trigger('g:upload.error', {
|
|
89
|
+
message: 'Error occurred uploading to S3 (' + xhr.status + ').'
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
xhr.upload.addEventListener('progress', (event) => {
|
|
95
|
+
this._xhrProgress(event);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
xhr.addEventListener('error', (event) => {
|
|
99
|
+
this.trigger('g:upload.error', {
|
|
100
|
+
message: 'Error occurred uploading to S3.',
|
|
101
|
+
event: event
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
xhr.send(this.params.file);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
prototype.resume = function () {
|
|
110
|
+
if (this.params.upload.s3.chunked) {
|
|
111
|
+
return this._sendNextChunk();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// If this is a single-chunk upload, we have to use the offset method
|
|
115
|
+
// to re-generate the initial request with a new timestamp.
|
|
116
|
+
restRequest({
|
|
117
|
+
url: 'file/offset',
|
|
118
|
+
method: 'GET',
|
|
119
|
+
data: {
|
|
120
|
+
uploadId: this.params.upload._id
|
|
121
|
+
},
|
|
122
|
+
error: null
|
|
123
|
+
}).done((resp) => {
|
|
124
|
+
this.params.upload.s3.request = resp;
|
|
125
|
+
this.execute();
|
|
126
|
+
}).fail((resp) => {
|
|
127
|
+
var msg;
|
|
128
|
+
|
|
129
|
+
if (resp.status === 0) {
|
|
130
|
+
msg = 'Could not connect to the Girder server.';
|
|
131
|
+
} else {
|
|
132
|
+
msg = 'An error occurred when resuming upload, check console.';
|
|
133
|
+
}
|
|
134
|
+
this.trigger('g:upload.error', {
|
|
135
|
+
message: msg
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* If the file being uploaded is larger than a single chunk length, it
|
|
142
|
+
* should be uploaded using the S3 multipart protocol.
|
|
143
|
+
*/
|
|
144
|
+
prototype._multiChunkUpload = function (xhr) {
|
|
145
|
+
this.eTagList = {};
|
|
146
|
+
this.startByte = 0;
|
|
147
|
+
this.chunkN = 1;
|
|
148
|
+
|
|
149
|
+
xhr.onload = () => {
|
|
150
|
+
if (xhr.status === 200) {
|
|
151
|
+
this.s3UploadId = xhr.responseText.match(/<UploadId>(.*)<\/UploadId>/).pop();
|
|
152
|
+
this._sendNextChunk();
|
|
153
|
+
} else {
|
|
154
|
+
this.trigger('g:upload.error', {
|
|
155
|
+
message: 'Error while initializing multichunk S3 upload.',
|
|
156
|
+
event: event
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
xhr.addEventListener('error', (event) => {
|
|
162
|
+
this.trigger('g:upload.error', {
|
|
163
|
+
message: 'Error occurred uploading to S3.',
|
|
164
|
+
event: event
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
xhr.send();
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Internal helper method used during multichunk upload protocol. This
|
|
173
|
+
* requests a signed chunk upload request from Girder, then uses that
|
|
174
|
+
* authorized request to send the chunk to S3.
|
|
175
|
+
*/
|
|
176
|
+
prototype._sendNextChunk = function () {
|
|
177
|
+
var sliceFn = this.params.file.webkitSlice ? 'webkitSlice' : 'slice';
|
|
178
|
+
var data = this.params.file[sliceFn](this.startByte,
|
|
179
|
+
this.startByte + this.params.upload.s3.chunkLength);
|
|
180
|
+
this.payloadLength = data.size;
|
|
181
|
+
|
|
182
|
+
// Get the authorized request from Girder
|
|
183
|
+
restRequest({
|
|
184
|
+
url: 'file/chunk',
|
|
185
|
+
method: 'POST',
|
|
186
|
+
data: {
|
|
187
|
+
offset: 0,
|
|
188
|
+
chunk: JSON.stringify({
|
|
189
|
+
s3UploadId: this.s3UploadId,
|
|
190
|
+
partNumber: this.chunkN,
|
|
191
|
+
contentLength: this.payloadLength
|
|
192
|
+
}),
|
|
193
|
+
uploadId: this.params.upload._id
|
|
194
|
+
},
|
|
195
|
+
error: null
|
|
196
|
+
}).done((resp) => {
|
|
197
|
+
// Send the chunk to S3
|
|
198
|
+
var xhr = new XMLHttpRequest();
|
|
199
|
+
xhr.open(resp.s3.request.method, resp.s3.request.url);
|
|
200
|
+
|
|
201
|
+
xhr.onload = () => {
|
|
202
|
+
if (xhr.status === 200) {
|
|
203
|
+
this.trigger('g:upload.chunkSent', {
|
|
204
|
+
bytes: this.payloadLength
|
|
205
|
+
});
|
|
206
|
+
this.eTagList[this.chunkN] = xhr.getResponseHeader('ETag');
|
|
207
|
+
this.startByte += this.payloadLength;
|
|
208
|
+
this.chunkN += 1;
|
|
209
|
+
|
|
210
|
+
if (this.startByte < this.params.file.size) {
|
|
211
|
+
this._sendNextChunk();
|
|
212
|
+
} else {
|
|
213
|
+
this._finalizeMultiChunkUpload();
|
|
214
|
+
}
|
|
215
|
+
} else {
|
|
216
|
+
this.trigger('g:upload.error', {
|
|
217
|
+
message: 'Error occurred uploading to S3 (' + xhr.status + ').'
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
xhr.upload.addEventListener('progress', (event) => {
|
|
223
|
+
this._xhrProgress(event);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
xhr.addEventListener('error', (event) => {
|
|
227
|
+
this.trigger('g:upload.error', {
|
|
228
|
+
message: 'Error occurred uploading to S3.',
|
|
229
|
+
event: event
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
xhr.send(data);
|
|
234
|
+
}).fail(() => {
|
|
235
|
+
this.trigger('g:upload.error', {
|
|
236
|
+
message: 'Error getting signed chunk request from Girder.'
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* When all chunks of a multichunk upload have been sent, this must be
|
|
243
|
+
* called in order to finalize the upload.
|
|
244
|
+
*/
|
|
245
|
+
prototype._finalizeMultiChunkUpload = function () {
|
|
246
|
+
restRequest({
|
|
247
|
+
url: 'file/completion',
|
|
248
|
+
method: 'POST',
|
|
249
|
+
data: {
|
|
250
|
+
uploadId: this.params.upload._id
|
|
251
|
+
},
|
|
252
|
+
error: null
|
|
253
|
+
}).done((resp) => {
|
|
254
|
+
this.trigger('g:upload.complete', resp);
|
|
255
|
+
}).fail((resp) => {
|
|
256
|
+
const msg = resp.status === 0
|
|
257
|
+
? 'Could not connect to the server.'
|
|
258
|
+
: 'Upload error during finalize, check console.';
|
|
259
|
+
this.trigger('g:upload.error', {
|
|
260
|
+
message: msg
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import $ from 'jquery';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Helper selector to enable/disable inputs elements
|
|
5
|
+
*
|
|
6
|
+
* @param enable Whether to enable the element or not
|
|
7
|
+
*/
|
|
8
|
+
$.fn.girderEnable = function (enable) {
|
|
9
|
+
var selection = $(this);
|
|
10
|
+
if (selection.is(':input')) {
|
|
11
|
+
selection.prop('disabled', !enable);
|
|
12
|
+
}
|
|
13
|
+
if (!enable) {
|
|
14
|
+
selection.addClass('disabled');
|
|
15
|
+
} else {
|
|
16
|
+
selection.removeClass('disabled');
|
|
17
|
+
}
|
|
18
|
+
return this;
|
|
19
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import $ from 'jquery';
|
|
2
|
+
|
|
3
|
+
import 'bootstrap/js/modal';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* This should be used instead of calling bootstrap's modal() jQuery
|
|
7
|
+
* method directly. This unbinds all previous events from the dialog,
|
|
8
|
+
* then calls modal on it and binds the bootstrap close events.
|
|
9
|
+
* @param view The view object. Pass "false" for special cases where the
|
|
10
|
+
* dialog does not correspond to a View object. Pass 'close'
|
|
11
|
+
* to just close an open modal dialog.
|
|
12
|
+
*/
|
|
13
|
+
$.fn.girderModal = function (view) {
|
|
14
|
+
/* If we have a modal dialog open, or one is in the process of closing,
|
|
15
|
+
* close that dialog before opening the new one. This prevents
|
|
16
|
+
* layering modal dialogs, but also makes sure that we don't have a
|
|
17
|
+
* problem switching from one modal dialog to another. */
|
|
18
|
+
if ($(this).is('.modal')) {
|
|
19
|
+
/* We have to reach into the backbone modal object a little to see
|
|
20
|
+
* if we need to do anything. By turning off the fade as we
|
|
21
|
+
* remove the old dialog, the removal is synchronous and we are
|
|
22
|
+
* sure it is gone before we add the new dialog. */
|
|
23
|
+
if ($(this).data('bs.modal') && $(this).data('bs.modal').isShown) {
|
|
24
|
+
var elem = $($(this).data('bs.modal').$element);
|
|
25
|
+
var hasFade = elem.hasClass('fade');
|
|
26
|
+
elem.removeClass('fade');
|
|
27
|
+
$(this).modal('hide');
|
|
28
|
+
elem.toggleClass('fade', hasFade);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (view !== 'close') {
|
|
32
|
+
this.off();
|
|
33
|
+
// It seems as if $foo.girderModal().on('shown.bs.modal', callback)
|
|
34
|
+
// does not trigger the callback because the call to modal() below is showing
|
|
35
|
+
// the modal (and sending the 'shown.bs.modal' event) *before* we get to
|
|
36
|
+
// register the event in .on('shown.bs.modal', cb). Let's show
|
|
37
|
+
// the modal in the next animation frame to fix this behavior for now.
|
|
38
|
+
setTimeout(() => {
|
|
39
|
+
this.modal().find('[data-dismiss="modal"]').off('click').on('click', () => {
|
|
40
|
+
this.modal('hide');
|
|
41
|
+
});
|
|
42
|
+
}, 0);
|
|
43
|
+
if (view !== false) {
|
|
44
|
+
view.delegateEvents();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return this;
|
|
48
|
+
};
|