'Export/import project' feature
parent
1981389445
commit
5c9ad4b19b
|
@ -1,10 +1,11 @@
|
|||
# Changelog #
|
||||
|
||||
## 1.5.0 ??? (unreleased)
|
||||
## 1.5.0 Betula Pendula - FOSDEM 2015 (unreleased)
|
||||
|
||||
### Features
|
||||
- Not showing closed milestones by default in backlog view.
|
||||
- In kanban view an archived user story status doesn't show his content by default.
|
||||
- Now you can export and import projects between Taiga instances.
|
||||
|
||||
### Misc
|
||||
- Lots of small and not so small bugfixes.
|
||||
|
|
|
@ -81,6 +81,8 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven
|
|||
{templateUrl: "admin/admin-project-default-values.html"})
|
||||
$routeProvider.when("/project/:pslug/admin/project-profile/modules",
|
||||
{templateUrl: "admin/admin-project-modules.html"})
|
||||
$routeProvider.when("/project/:pslug/admin/project-profile/export",
|
||||
{templateUrl: "admin/admin-project-export.html"})
|
||||
$routeProvider.when("/project/:pslug/admin/project-values/us-status",
|
||||
{templateUrl: "admin/admin-project-values-us-status.html"})
|
||||
$routeProvider.when("/project/:pslug/admin/project-values/us-points",
|
||||
|
@ -186,7 +188,8 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven
|
|||
|
||||
# If there is an error in the version throw a notify error
|
||||
versionCheckHttpIntercept = ($q, $confirm) ->
|
||||
versionErrorMsg = "Someone inside Taiga has changed this before and our Oompa Loompas cannot apply your changes. Please reload and apply your changes again (they will be lost)." #TODO: i18n
|
||||
versionErrorMsg = "Someone inside Taiga has changed this before and our Oompa Loompas cannot apply your changes.
|
||||
Please reload and apply your changes again (they will be lost)." #TODO: i18n
|
||||
|
||||
httpResponseError = (response) ->
|
||||
if response.status == 400 && response.data.version
|
||||
|
@ -202,7 +205,7 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven
|
|||
|
||||
$provide.factory("versionCheckHttpIntercept", ["$q", "$tgConfirm", versionCheckHttpIntercept])
|
||||
|
||||
$httpProvider.interceptors.push('versionCheckHttpIntercept')
|
||||
$httpProvider.interceptors.push('versionCheckHttpIntercept');
|
||||
|
||||
window.checksley.updateValidators({
|
||||
linewidth: (val, width) ->
|
||||
|
|
|
@ -117,7 +117,7 @@ ProjectProfileDirective = ($repo, $confirm, $loading, $navurls, $location) ->
|
|||
if data._error_message
|
||||
$confirm.notify("error", data._error_message)
|
||||
|
||||
submitButton = $el.find(".submit-button");
|
||||
submitButton = $el.find(".submit-button")
|
||||
|
||||
$el.on "submit", "form", submit
|
||||
$el.on "click", ".submit-button", submit
|
||||
|
@ -210,3 +210,89 @@ ProjectModulesDirective = ($repo, $confirm, $loading) ->
|
|||
return {link:link}
|
||||
|
||||
module.directive("tgProjectModules", ["$tgRepo", "$tgConfirm", "$tgLoading", ProjectModulesDirective])
|
||||
|
||||
|
||||
#############################################################################
|
||||
## Project Export Directive
|
||||
#############################################################################
|
||||
|
||||
ProjectExportDirective = ($window, $rs, $confirm) ->
|
||||
link = ($scope, $el, $attrs) ->
|
||||
buttonsEl = $el.find(".admin-project-export-buttons")
|
||||
showButtons = -> buttonsEl.removeClass("hidden")
|
||||
hideButtons = -> buttonsEl.addClass("hidden")
|
||||
|
||||
resultEl = $el.find(".admin-project-export-result")
|
||||
showResult = -> resultEl.removeClass("hidden")
|
||||
hideResult = -> resultEl.addClass("hidden")
|
||||
|
||||
spinnerEl = $el.find(".spin")
|
||||
showSpinner = -> spinnerEl.removeClass("hidden")
|
||||
hideSpinner = -> spinnerEl.addClass("hidden")
|
||||
|
||||
resultTitleEl = $el.find(".result-title")
|
||||
setLoadingTitle = -> resultTitleEl.html("We are generating your dump file") # TODO: i18n
|
||||
setAsyncTitle = -> resultTitleEl.html("We are generating your dump file") # TODO: i18n
|
||||
setSyncTitle = -> resultTitleEl.html("Your dump file ir ready!") # TODO: i18n
|
||||
|
||||
resultMessageEl = $el.find(".result-message ")
|
||||
setLoadingMessage = -> resultMessageEl.html("Please don't close this page.") # TODO: i18n
|
||||
setAsyncMessage = -> resultMessageEl.html("We will send you an email when ready.") # TODO: i18n
|
||||
setSyncMessage = (url) -> resultMessageEl.html("If the download doesn't start automatically click
|
||||
<a href='#{url}' download title='Download
|
||||
the dump file'>here.") # TODO: i18n
|
||||
|
||||
showLoadingMode = ->
|
||||
showSpinner()
|
||||
setLoadingTitle()
|
||||
setLoadingMessage()
|
||||
hideButtons()
|
||||
showResult()
|
||||
|
||||
showExportResultAsyncMode = ->
|
||||
hideSpinner()
|
||||
setAsyncTitle()
|
||||
setAsyncMessage()
|
||||
|
||||
showExportResultSyncMode = (url) ->
|
||||
hideSpinner()
|
||||
setSyncTitle()
|
||||
setSyncMessage(url)
|
||||
|
||||
showErrorMode = ->
|
||||
hideSpinner()
|
||||
hideResult()
|
||||
showButtons()
|
||||
|
||||
$el.on "click", "a.button-export", debounce 2000, (event) =>
|
||||
event.preventDefault()
|
||||
|
||||
onSuccess = (result) =>
|
||||
if result.status == 202 # Async mode
|
||||
showExportResultAsyncMode()
|
||||
else #result.status == 200 # Sync mode
|
||||
dumpUrl = result.data.url
|
||||
showExportResultSyncMode(dumpUrl)
|
||||
$window.open(dumpUrl, "_blank")
|
||||
|
||||
onError = (result) =>
|
||||
showErrorMode()
|
||||
|
||||
errorMsg = "Our oompa loompas have some problems generasting your dump.
|
||||
Please try again. " # TODO: i18n
|
||||
|
||||
if result.status == 429 # TOO MANY REQUESTS
|
||||
errorMsg = "Sorry, our oompa loompas are very busy right now.
|
||||
Please try again in a few minutes. " # TODO: i18n
|
||||
else if result.data?._error_message
|
||||
errorMsg = "Our oompa loompas have some problems generasting your dump:
|
||||
#{result.data._error_message}" # TODO: i18n
|
||||
|
||||
$confirm.notify("error", errorMsg)
|
||||
|
||||
showLoadingMode()
|
||||
$rs.projects.export($scope.projectId).then(onSuccess, onError)
|
||||
|
||||
return {link:link}
|
||||
|
||||
module.directive("tgProjectExport", ["$window", "$tgResources", "$tgConfirm", ProjectExportDirective])
|
||||
|
|
|
@ -83,6 +83,7 @@ urls = {
|
|||
"project-admin-project-profile-details": "/project/:project/admin/project-profile/details"
|
||||
"project-admin-project-profile-default-values": "/project/:project/admin/project-profile/default-values"
|
||||
"project-admin-project-profile-modules": "/project/:project/admin/project-profile/modules"
|
||||
"project-admin-project-profile-export": "/project/:project/admin/project-profile/export"
|
||||
"project-admin-project-values-us-status": "/project/:project/admin/project-values/us-status"
|
||||
"project-admin-project-values-us-points": "/project/:project/admin/project-values/us-points"
|
||||
"project-admin-project-values-task-status": "/project/:project/admin/project-values/task-status"
|
||||
|
|
|
@ -148,11 +148,12 @@ class ConfirmService extends taiga.Service
|
|||
|
||||
return defered.promise
|
||||
|
||||
success: (message) ->
|
||||
success: (title, message) ->
|
||||
el = angular.element(".lightbox-generic-success")
|
||||
|
||||
# Render content
|
||||
el.find("h2.title").html(message)
|
||||
el.find("h2.title").html(title) if title
|
||||
el.find("p.message").html(message) if message
|
||||
defered = @q.defer()
|
||||
|
||||
# Assign event handlers
|
||||
|
@ -170,6 +171,30 @@ class ConfirmService extends taiga.Service
|
|||
|
||||
return defered.promise
|
||||
|
||||
loader: (title, message) ->
|
||||
el = angular.element(".lightbox-generic-loading")
|
||||
|
||||
# Render content
|
||||
el.find("h2.title").html(title) if title
|
||||
el.find("p.message").html(message) if message
|
||||
|
||||
return {
|
||||
start: => @lightboxService.open(el)
|
||||
stop: => @lightboxService.close(el)
|
||||
update: (status, title, message, percent) =>
|
||||
el.find("h2.title").html(title) if title
|
||||
el.find("p.message").html(message) if message
|
||||
|
||||
if percent
|
||||
el.find(".spin").addClass("hidden")
|
||||
el.find(".progress-bar-wrapper").removeClass("hidden")
|
||||
el.find(".progress-bar-wrapper > .bar").width(percent + '%')
|
||||
el.find(".progress-bar-wrapper > span").html(percent + '%').css('left', (percent - 9) + '%' )
|
||||
else
|
||||
el.find(".spin").removeClass("hidden")
|
||||
el.find(".progress-bar-wrapper").addClass("hidden")
|
||||
}
|
||||
|
||||
notify: (type, message, title, time) ->
|
||||
# NOTE: Typesi are: error, success, light-error
|
||||
# See partials/components/notification-message.jade)
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
###
|
||||
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
|
||||
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
|
||||
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# File: modules/common/importer.coffee
|
||||
###
|
||||
|
||||
module = angular.module("taigaCommon")
|
||||
|
||||
|
||||
ImportProjectButtonDirective = ($rs, $confirm, $location, $navUrls) ->
|
||||
link = ($scope, $el, $attrs) ->
|
||||
$el.on "click", ".import-project-button", (event) ->
|
||||
event.preventDefault()
|
||||
$el.find("input.import-file").val("")
|
||||
$el.find("input.import-file").trigger("click")
|
||||
|
||||
$el.on "change", "input.import-file", (event) ->
|
||||
event.preventDefault()
|
||||
file = event.target.files[0]
|
||||
return if not file
|
||||
|
||||
loader = $confirm.loader("Uploading dump file")
|
||||
|
||||
onSuccess = (result) ->
|
||||
loader.stop()
|
||||
if result.status == 202 # Async mode
|
||||
title = "Our Oompa Loompas are importing your project" # TODO: i18n
|
||||
message = "This process could take a few minutes <br/> We will send you
|
||||
an email when ready" # TODO: i18n
|
||||
$confirm.success(title, message)
|
||||
|
||||
else # result.status == 201 # Sync mode
|
||||
ctx = {project: result.data.slug}
|
||||
$location.path($navUrls.resolve("project-admin-project-profile-details", ctx))
|
||||
$confirm.notify("success", "Your project has been imported successfuly.") # TODO: i18n
|
||||
|
||||
onError = (result) ->
|
||||
loader.stop()
|
||||
console.log "Error", result
|
||||
errorMsg = "Our oompa loompas have some problems importing your dump data.
|
||||
Please try again. " # TODO: i18n
|
||||
|
||||
if result.status == 429 # TOO MANY REQUESTS
|
||||
errorMsg = "Sorry, our oompa loompas are very busy right now.
|
||||
Please try again in a few minutes. " # TODO: i18n
|
||||
else if result.data?._error_message
|
||||
errorMsg = "Our oompa loompas have some problems importing your dump data:
|
||||
#{result.data._error_message}" # TODO: i18n
|
||||
|
||||
$confirm.notify("error", errorMsg)
|
||||
|
||||
loader.start()
|
||||
$rs.projects.import(file, loader.update).then(onSuccess, onError)
|
||||
|
||||
return {link: link}
|
||||
|
||||
module.directive("tgImportProjectButton", ["$tgResources", "$tgConfirm", "$location", "$tgNavUrls",
|
||||
ImportProjectButtonDirective])
|
|
@ -26,7 +26,8 @@ class TgLoadingService extends taiga.Service
|
|||
if not target.hasClass('loading')
|
||||
target.data('loading-old-content', target.html())
|
||||
target.addClass('loading')
|
||||
target.html("<span class='icon icon-spinner'></span>")
|
||||
target.html("<img class='loading-spinner' src='/svg/spinner-circle.svg' alt='loading...' />")
|
||||
debugger
|
||||
|
||||
finish: (target) ->
|
||||
if target.hasClass('loading')
|
||||
|
|
|
@ -147,7 +147,7 @@ ProjectsNavigationDirective = ($rootscope, animationFrame, $timeout, tgLoader, $
|
|||
|
||||
loadingStart = new Date().getTime()
|
||||
|
||||
$el.on "click", ".create-project-button .button", (event) ->
|
||||
$el.on "click", ".create-project-button", (event) ->
|
||||
event.preventDefault()
|
||||
$ctrl.newProject()
|
||||
|
||||
|
@ -168,7 +168,8 @@ ProjectsNavigationDirective = ($rootscope, animationFrame, $timeout, tgLoader, $
|
|||
}
|
||||
|
||||
|
||||
module.directive("tgProjectsNav", ["$rootScope", "animationFrame", "$timeout", "tgLoader", "$tgLocation", "$compile", "$tgTemplate", ProjectsNavigationDirective])
|
||||
module.directive("tgProjectsNav", ["$rootScope", "animationFrame", "$timeout", "tgLoader", "$tgLocation", "$compile",
|
||||
"$tgTemplate", ProjectsNavigationDirective])
|
||||
|
||||
|
||||
#############################################################################
|
||||
|
|
|
@ -100,6 +100,10 @@ urls = {
|
|||
|
||||
# Feedback
|
||||
"feedback": "/feedback"
|
||||
|
||||
# Export/Import
|
||||
"exporter": "/exporter"
|
||||
"importer": "/importer/load_dump"
|
||||
}
|
||||
|
||||
# Initialize api urls service
|
||||
|
|
|
@ -21,15 +21,17 @@
|
|||
|
||||
|
||||
taiga = @.taiga
|
||||
sizeFormat = @.taiga.sizeFormat
|
||||
|
||||
resourceProvider = ($repo, $http, $urls) ->
|
||||
|
||||
resourceProvider = ($config, $repo, $http, $urls, $auth, $q, $rootScope) ->
|
||||
service = {}
|
||||
|
||||
service.get = (id) ->
|
||||
return $repo.queryOne("projects", id)
|
||||
service.get = (projectId) ->
|
||||
return $repo.queryOne("projects", projectId)
|
||||
|
||||
service.getBySlug = (slug) ->
|
||||
return $repo.queryOne("projects", "by_slug?slug=#{slug}")
|
||||
service.getBySlug = (projectSlug) ->
|
||||
return $repo.queryOne("projects", "by_slug?slug=#{projectSlug}")
|
||||
|
||||
service.list = ->
|
||||
return $repo.queryMany("projects")
|
||||
|
@ -55,12 +57,73 @@ resourceProvider = ($repo, $http, $urls) ->
|
|||
service.memberStats = (projectId) ->
|
||||
return $repo.queryOneRaw("projects", "#{projectId}/member_stats")
|
||||
|
||||
service.tagsColors = (id) ->
|
||||
return $repo.queryOne("projects", "#{id}/tags_colors")
|
||||
service.tagsColors = (projectId) ->
|
||||
return $repo.queryOne("projects", "#{projectId}/tags_colors")
|
||||
|
||||
service.export = (projectId) ->
|
||||
url = "#{$urls.resolve("exporter")}/#{projectId}"
|
||||
return $http.get(url)
|
||||
|
||||
service.import = (file, statusUpdater) ->
|
||||
defered = $q.defer()
|
||||
|
||||
maxFileSize = $config.get("maxUploadFileSize", null)
|
||||
if maxFileSize and file.size > maxFileSize
|
||||
response = {
|
||||
status: 413,
|
||||
data: _error_message: "'#{file.name}' (#{sizeFormat(file.size)}) is too heavy for our oompa
|
||||
loompas, try it with a smaller than (#{sizeFormat(maxFileSize)})"
|
||||
}
|
||||
defered.reject(response)
|
||||
return defered.promise
|
||||
|
||||
uploadProgress = (evt) =>
|
||||
percent = Math.round((evt.loaded / evt.total) * 100)
|
||||
message = "Uloaded #{sizeFormat(evt.loaded)} of #{sizeFormat(evt.total)}"
|
||||
statusUpdater("in-progress", null, message, percent)
|
||||
|
||||
uploadComplete = (evt) =>
|
||||
statusUpdater("done", "Importing Project", "This process can take a while, please keep the window open.") # i18n
|
||||
|
||||
uploadFailed = (evt) =>
|
||||
statusUpdater("error")
|
||||
|
||||
complete = (evt) =>
|
||||
response = {}
|
||||
try
|
||||
response.data = JSON.parse(evt.target.responseText)
|
||||
catch
|
||||
response.data = {}
|
||||
response.status = evt.target.status
|
||||
|
||||
defered.resolve(response) if response.status in [201, 202]
|
||||
defered.reject(response)
|
||||
|
||||
failed = (evt) =>
|
||||
defered.reject("fail")
|
||||
|
||||
data = new FormData()
|
||||
data.append('dump', file)
|
||||
|
||||
xhr = new XMLHttpRequest()
|
||||
xhr.upload.addEventListener("progress", uploadProgress, false)
|
||||
xhr.upload.addEventListener("load", uploadComplete, false)
|
||||
xhr.upload.addEventListener("error", uploadFailed, false)
|
||||
xhr.upload.addEventListener("abort", uploadFailed, false)
|
||||
xhr.addEventListener("load", complete, false)
|
||||
xhr.addEventListener("error", failed, false)
|
||||
|
||||
xhr.open("POST", $urls.resolve("importer"))
|
||||
xhr.setRequestHeader("Authorization", "Bearer #{$auth.getToken()}")
|
||||
xhr.setRequestHeader('Accept', 'application/json')
|
||||
xhr.send(data)
|
||||
|
||||
return defered.promise
|
||||
|
||||
return (instance) ->
|
||||
instance.projects = service
|
||||
|
||||
|
||||
module = angular.module("taigaResources")
|
||||
module.factory("$tgProjectsResourcesProvider", ["$tgRepo", "$tgHttp", "$tgUrls", resourceProvider])
|
||||
module.factory("$tgProjectsResourcesProvider", ["$tgConfig", "$tgRepo", "$tgHttp", "$tgUrls", "$tgAuth", "$q",
|
||||
resourceProvider])
|
||||
|
|
|
@ -145,7 +145,7 @@ UserAvatarDirective = ($auth, $model, $rs, $confirm) ->
|
|||
|
||||
$el.on "change", "#avatar-field", (event) ->
|
||||
if $scope.avatarAttachment
|
||||
$el.find('.overlay').show()
|
||||
$el.find('.overlay').css('display', 'flex')
|
||||
$rs.userSettings.changeAvatar($scope.avatarAttachment).then(onSuccess, onError)
|
||||
|
||||
# Use gravatar photo
|
||||
|
|
|
@ -29,6 +29,8 @@ html(lang="en")
|
|||
include partials/includes/modules/lightbox-generic-success
|
||||
div.lightbox.lightbox-generic-error
|
||||
include partials/includes/modules/lightbox-generic-error
|
||||
div.lightbox.lightbox-generic-loading
|
||||
include partials/includes/modules/lightbox-generic-loading
|
||||
div.lightbox.lightbox-search(tg-search-box)
|
||||
include partials/includes/modules/lightbox-search
|
||||
div.lightbox.lightbox-feedback.lightbox-generic-form(tg-lb-feedback)
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
div.wrapper(ng-controller="ProjectProfileController as ctrl",
|
||||
ng-init="section='admin'; sectionName='Export'")
|
||||
sidebar.menu-secondary.sidebar(tg-admin-navigation="project-profile")
|
||||
include ../includes/modules/admin-menu
|
||||
|
||||
sidebar.menu-tertiary.sidebar(tg-admin-navigation="export")
|
||||
include ../includes/modules/admin-submenu-project-profile
|
||||
|
||||
section.main.admin-common(tg-project-export)
|
||||
header
|
||||
include ../includes/components/mainTitle
|
||||
p.admin-subtitle Export your project to save a backup or to create a new one based on this.
|
||||
|
||||
div.admin-project-export-buttons
|
||||
a.button.button-green.button-export(href="", title="Export your project") Export
|
||||
|
||||
div.admin-project-export-result.hidden
|
||||
div.spin.hidden
|
||||
img(src="/svg/spinner-circle.svg", alt="loading...")
|
||||
h3.result-title
|
||||
p.result-message
|
|
@ -1,4 +1,4 @@
|
|||
div.loading
|
||||
div.loading-bar
|
||||
span.item.item-1
|
||||
span.item.item-2
|
||||
span.item.item-3
|
||||
|
|
|
@ -16,3 +16,7 @@ section.admin-submenu
|
|||
a(href="", tg-nav="project-admin-project-profile-modules:project=project.slug")
|
||||
span.title Modules
|
||||
span.icon.icon-arrow-right
|
||||
li#adminmenu-export
|
||||
a(href="", tg-nav="project-admin-project-profile-export:project=project.slug")
|
||||
span.title Export
|
||||
span.icon.icon-arrow-right
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
//- See $confirm.loader to undestand this template
|
||||
|
||||
section
|
||||
div.spin.hidden
|
||||
img(src="/svg/spinner-circle.svg", alt="loading...")
|
||||
|
||||
div.progress-bar-wrapper.hidden
|
||||
div.bar
|
||||
span.progress
|
||||
|
||||
h2.title
|
||||
p.message
|
|
@ -2,6 +2,7 @@ a.close(href="", title="close")
|
|||
span.icon.icon-delete
|
||||
section
|
||||
h2.title
|
||||
p.message
|
||||
div.options
|
||||
a.button.button-green(href="", title="Accept")
|
||||
span Accept
|
||||
|
|
|
@ -4,8 +4,14 @@ form
|
|||
input(type="text", placeholder="Search in...", class="search-project")
|
||||
a(class="icon icon-search")
|
||||
|
||||
div(class="create-project-button")
|
||||
a(class="button button-green" href="") Create project
|
||||
div(class="create-project-button-wrapper")
|
||||
a(class="button button-green create-project-button" href="" title="Create new project")
|
||||
Create project
|
||||
|
||||
div(tg-import-project-button)
|
||||
a(class="button button-blackish import-project-button" href="" title="Import project")
|
||||
span(class="icon icon-upload")
|
||||
input(class="import-file hidden" type="file")
|
||||
|
||||
div(class="projects-pagination" tg-projects-pagination)
|
||||
a(class="v-pagination-previous icon icon-arrow-up", href="")
|
||||
|
|
|
@ -25,4 +25,10 @@ div.home-projects-list(ng-controller="ProjectsController as ctrl")
|
|||
div(tg-projects-list)
|
||||
|
||||
.create-project-button-wrapper
|
||||
a.button.button-green(href="", ng-click="ctrl.newProject()") Create project
|
||||
a.button.button-green.create-project-button(href="", ng-click="ctrl.newProject()",
|
||||
title="Create new project") Create project
|
||||
div(tg-import-project-button)
|
||||
a.button.button-blackish.import-project-button(href="", title="Import project")
|
||||
span.icon.icon-upload
|
||||
input.import-file.hidden(type="file")
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
.loading {
|
||||
.loading-bar {
|
||||
align-items: stretch;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
|
|
@ -58,6 +58,9 @@
|
|||
padding: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
.loading-spinner {
|
||||
@extend %loading-spinner;
|
||||
}
|
||||
}
|
||||
|
||||
%button {
|
||||
|
@ -91,3 +94,12 @@
|
|||
background: url('/images/invitation_bg.jpg') no-repeat center center;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
%loading-spinner {
|
||||
animation-timing-function: ease-in-out;
|
||||
animation: rotate 1.5s cubic-bezier(.00, .05, .87, 1.04) infinite alternate;
|
||||
margin: 0 auto;
|
||||
max-height: 1rem;
|
||||
max-width: 1rem;
|
||||
transform-origin: 32 32;
|
||||
}
|
||||
|
|
|
@ -10,6 +10,20 @@
|
|||
}
|
||||
}
|
||||
|
||||
//Spin
|
||||
@-webkit-keyframes rotate {
|
||||
50% {
|
||||
filter: invert(1);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
50% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@include keyframes(formSlide) {
|
||||
0% {
|
||||
filter: blur(5px);
|
||||
|
|
|
@ -39,10 +39,6 @@ sup {
|
|||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.icon-spinner {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
.clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
|
|
@ -86,8 +86,7 @@
|
|||
width: 100%;
|
||||
}
|
||||
.icon-edit,
|
||||
.icon-floppy,
|
||||
.icon-spinner {
|
||||
.icon-floppy {
|
||||
@extend %large;
|
||||
color: $gray-light;
|
||||
margin-left: .5rem;
|
||||
|
@ -125,6 +124,11 @@
|
|||
margin-right: 5rem;
|
||||
}
|
||||
}
|
||||
.loading-spinner {
|
||||
@extend %loading-spinner;
|
||||
max-height: 1.5rem;
|
||||
max-width: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.blocked-warning {
|
||||
|
@ -187,15 +191,15 @@
|
|||
position: absolute;
|
||||
right: 1rem;
|
||||
top: .2rem;
|
||||
.save {
|
||||
color: $blackish;
|
||||
opacity: .6;
|
||||
top: 0;
|
||||
}
|
||||
&:hover {
|
||||
opacity: .3;
|
||||
transition: opacity .2s linear;
|
||||
}
|
||||
.loading-spinner {
|
||||
@extend %loading-spinner;
|
||||
max-height: 1.5rem;
|
||||
max-width: 1.5rem;
|
||||
}
|
||||
}
|
||||
.edit {
|
||||
color: $grayer;
|
||||
|
@ -344,9 +348,8 @@
|
|||
.level-name {
|
||||
color: darken($whitish, 20%);
|
||||
float: right;
|
||||
&.loading span {
|
||||
animation: loading .5s linear;
|
||||
animation: spin 1s linear infinite;
|
||||
.loading-spinner {
|
||||
@extend %loading-spinner;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -413,6 +416,9 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
.loading-spinner {
|
||||
@extend %loading-spinner;
|
||||
}
|
||||
}
|
||||
|
||||
.us-status {
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
.admin-project-export-buttons {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.admin-project-export-result {
|
||||
margin-top: 1rem;
|
||||
.spin {
|
||||
margin: 0 auto;
|
||||
width: 2.5rem;
|
||||
img {
|
||||
@extend %loading-spinner;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
h3 {
|
||||
@extend %bold;
|
||||
@extend %large;
|
||||
background: $whitish;
|
||||
color: $gray;
|
||||
margin: .5rem;
|
||||
padding: .5rem;
|
||||
text-align: center;
|
||||
}
|
||||
p {
|
||||
color: $gray-light;
|
||||
margin: .5rem 0;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
|
@ -10,16 +10,10 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
&.loading {
|
||||
width: 100%;
|
||||
span {
|
||||
animation: loading .5s linear;
|
||||
animation: spin 1s linear infinite;
|
||||
font-size: 30px;
|
||||
padding: 20px 0;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
.loading-spinner {
|
||||
@extend %loading-spinner;
|
||||
margin: 0 auto;
|
||||
max-width: 2rem;
|
||||
}
|
||||
.user-avatar {
|
||||
flex-grow: 1;
|
||||
|
|
|
@ -108,6 +108,12 @@
|
|||
.preview-icon {
|
||||
opacity: 0;
|
||||
}
|
||||
.loading-spinner {
|
||||
@extend %loading-spinner;
|
||||
max-height: 1rem;
|
||||
max-width: 1rem;
|
||||
}
|
||||
|
||||
}
|
||||
.show-more-comments {
|
||||
@extend %small;
|
||||
|
|
|
@ -389,15 +389,57 @@
|
|||
|
||||
|
||||
.lightbox-generic-success,
|
||||
.lightbox-generic-error {
|
||||
.lightbox-generic-error,
|
||||
.lightbox-generic-loading {
|
||||
section {
|
||||
flex-basis: 420px;
|
||||
flex-basis: 500px;
|
||||
flex-grow: 0;
|
||||
width: 420px;
|
||||
flex-shrink: 0;
|
||||
width: 500px;
|
||||
}
|
||||
h2 {
|
||||
line-height: 2rem;
|
||||
}
|
||||
p {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.lightbox-generic-loading {
|
||||
.spin {
|
||||
margin: 1rem auto;
|
||||
width: 5rem;
|
||||
img {
|
||||
@extend %loading-spinner;
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
.progress-bar-wrapper {
|
||||
background: darken($whitish, 5%);
|
||||
height: 30px;
|
||||
margin-bottom: 1rem;
|
||||
padding: 3px;
|
||||
position: relative;
|
||||
.bar {
|
||||
background: $fresh-taiga;
|
||||
height: 24px;
|
||||
position: absolute;
|
||||
transition: width .1s linear;
|
||||
}
|
||||
.progress {
|
||||
@extend %title;
|
||||
@extend %bold;
|
||||
@extend %large;
|
||||
background: darken($whitish, 5%);
|
||||
bottom: 35px;
|
||||
color: $gray;
|
||||
padding: .3rem;
|
||||
position: absolute;
|
||||
transition: left .1s linear;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.lightbox-create-issue {
|
||||
|
|
|
@ -42,12 +42,24 @@
|
|||
flex-direction: column;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
.create-project-button {
|
||||
.create-project-button-wrapper {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
margin-top: 1rem;
|
||||
a {
|
||||
.create-project-button {
|
||||
flex-grow: 8;
|
||||
margin-right: .2rem;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
.import-project-button {
|
||||
flex-grow: 1;
|
||||
padding-left: .5rem;
|
||||
padding-right: .5rem;
|
||||
text-align: center;
|
||||
.icon {
|
||||
color: $grayer;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.v-pagination-previous,
|
||||
|
|
|
@ -35,20 +35,14 @@
|
|||
opacity: 0;
|
||||
transition: all .1s ease-in;
|
||||
.loading {
|
||||
background: $grayer;
|
||||
border: 1px solid #b8b8b8;
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
padding: 8px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
.icon-spinner {
|
||||
color: $whitish;
|
||||
float: none;
|
||||
}
|
||||
span {
|
||||
animation: loading .5s linear;
|
||||
animation: spin 1s linear infinite;
|
||||
.loading-spinner {
|
||||
@extend %loading-spinner;
|
||||
max-height: 1rem;
|
||||
max-width: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -76,7 +76,6 @@
|
|||
height: 130px;
|
||||
margin-bottom: 1rem;
|
||||
margin-right: 1rem;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
transition: background-color .3s linear;
|
||||
width: 23.5%;
|
||||
|
@ -158,8 +157,24 @@
|
|||
width: 100%;
|
||||
}
|
||||
.create-project-button-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
margin-top: 1rem;
|
||||
.create-project-button {
|
||||
flex-grow: 8;
|
||||
margin-right: .2rem;
|
||||
text-align: center;
|
||||
}
|
||||
.import-project-button {
|
||||
flex-grow: 1;
|
||||
padding-left: .5rem;
|
||||
padding-right: .5rem;
|
||||
text-align: center;
|
||||
.icon {
|
||||
color: $grayer;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.button-green {
|
||||
color: $whitish;
|
||||
|
|
|
@ -101,6 +101,7 @@ $column-margin: 0 10px 0 0;
|
|||
margin: $column-margin;
|
||||
max-width: $column-width;
|
||||
overflow-y: auto;
|
||||
widows: $column-width;
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
|
|
@ -12,27 +12,29 @@
|
|||
.image-container {
|
||||
position: relative;
|
||||
}
|
||||
img {
|
||||
border: 2px solid $white;
|
||||
.avatar {
|
||||
border-radius: 8%;
|
||||
width: 100%;
|
||||
}
|
||||
.overlay {
|
||||
background: rgba($white, .9);
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
background: rgba($blackish, .9);
|
||||
bottom: 0;
|
||||
display: none;
|
||||
justify-content: center;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
}
|
||||
.icon-spinner {
|
||||
@extend %xlarge;
|
||||
animation: spin infinite .8s linear;
|
||||
color: $gray-light;
|
||||
left: 40%;
|
||||
position: absolute;
|
||||
top: 40%;
|
||||
.loading-spinner {
|
||||
@extend %loading-spinner;
|
||||
border: 0;
|
||||
flex-grow: 0;
|
||||
transform-origin: 32 32;
|
||||
width: 30%;
|
||||
}
|
||||
p {
|
||||
@extend %xsmall;
|
||||
|
|
|
@ -31,18 +31,14 @@
|
|||
}
|
||||
}
|
||||
&.loading {
|
||||
background: $grayer;
|
||||
border: 1px solid #b8b8b8;
|
||||
margin: 0;
|
||||
padding: 8px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
.icon-spinner {
|
||||
color: $whitish;
|
||||
float: none;
|
||||
}
|
||||
span {
|
||||
animation: loading .5s linear, spin 1s linear infinite;
|
||||
.loading-spinner {
|
||||
@extend %loading-spinner;
|
||||
max-height: 1rem;
|
||||
max-width: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64" version="1.1"><style>.s0{fill:#729fcf;}.s1{fill:#3465a4;}.s2{fill:#204a87;}.s3{fill:#fce94f;}.s4{fill:#edd400;}.s5{fill:#c4a000;}.s6{fill:#8ae234;}.s7{fill:#73d216;}.s8{fill:#4e9a06;}.s9{fill:#ef2929;}.s10{fill:#c00;}.s11{fill:#ce5c00;}</style><path d="M13 13 9.4 9.4c0 0-2 2.1-3 3.4-1 1.2-2.1 3.3-2.1 3.3l4.4 2.5M13 13 9.4 9.4c0 0-2 2.1-3 3.4-1 1.2-2.1 3.3-2.1 3.3l4.4 2.5M13 13 9.4 9.4c0 0-2 2.1-3 3.4-1 1.2-2.1 3.3-2.1 3.3l4.4 2.5" fill="#729fcf"/><path d="m18.6 8.7-2.5-4.4c0 0-2.5 1.5-3.7 2.5-1.3 1-2.9 2.6-2.9 2.6l3.6 3.6M25.1 6 23.7 1.1c0 0-2.8 0.8-4.2 1.4-1.5 0.6-3.5 1.8-3.5 1.8l2.5 4.4" fill="#3465a4"/><path d="m32 5.1 0-5.1c0 0-2.9 0.1-4.5 0.3-1.6 0.2-3.8 0.8-3.8 0.8l1.3 4.9M39 6l1.3-4.9M39 6l1.3-4.9M39 6l1.3-4.9c0 0-2.8-0.7-4.4-0.9C34.4 0 32 0 32 0l0 5.1" fill="#204a87"/><path d="m45.6 8.7 2.6-4.4c0 0-2.5-1.4-4-2C42.7 1.7 40.4 1.1 40.4 1.1l-1.3 4.9M51 13l3.6-3.6M51 13l3.6-3.6M51 13l3.6-3.6c0 0-2.1-2-3.4-3-1.2-1-3.3-2.1-3.3-2.1l-2.5 4.4" fill="#fce94f"/><path d="m55.3 18.6 4.4-2.5c0 0-1.5-2.5-2.5-3.7-1-1.3-2.6-2.9-2.6-2.9l-3.6 3.6M58 25.1l4.9-1.3M58 25.1l4.9-1.3M58 25.1l4.9-1.3c0 0-0.8-2.8-1.4-4.2-0.6-1.5-1.8-3.5-1.8-3.5l-4.4 2.5" fill="#edd400"/><path d="m58.9 32 5.1 0c0 0-0.1-2.9-0.3-4.5-0.2-1.6-0.8-3.8-0.8-3.8l-4.9 1.3M58 39l4.9 1.3M58 39l4.9 1.3M58 39l4.9 1.3c0 0 0.7-2.8 0.9-4.4 0.2-1.6 0.2-3.9 0.2-3.9l-5.1 0" fill="#c4a000"/><path d="m55.3 45.5 4.4 2.6c0 0 1.4-2.5 2-4 0.6-1.5 1.2-3.7 1.2-3.7l-4.9-1.3M51 51l3.6 3.6M51 51l3.6 3.6M51 51l3.6 3.6c0 0 2-2.1 3-3.4 1-1.2 2.1-3.3 2.1-3.3l-4.4-2.5" fill="#8ae234"/><path d="m45.4 55.3 2.5 4.4c0 0 2.5-1.5 3.7-2.5 1.3-1 2.9-2.6 2.9-2.6l-3.6-3.6M38.9 58l1.3 4.9M38.9 58l1.3 4.9M38.9 58l1.3 4.9c0 0 2.8-0.8 4.2-1.4 1.5-0.6 3.5-1.8 3.5-1.8l-2.5-4.4" fill="#73d216"/><path d="m32 58.9 0 5.1c0 0 2.9-0.1 4.5-0.3 1.6-0.2 3.8-0.8 3.8-0.8l-1.3-4.9M25 58l-1.3 4.9M25 58l-1.3 4.9M25 58l-1.3 4.9c0 0 2.8 0.7 4.4 0.9 1.6 0.2 3.9 0.2 3.9 0.2l0-5.1" fill="#4e9a06"/><path d="m18.5 55.3-2.6 4.4c0 0 2.5 1.4 4 2 1.5 0.6 3.7 1.2 3.7 1.2l1.3-4.9M13 51l-3.6 3.6M13 51l-3.6 3.6M13 51l-3.6 3.6c0 0 2.1 2 3.4 3 1.2 1 3.3 2.1 3.3 2.1l2.5-4.4" fill="#ef2929"/><path d="m8.7 45.4-4.4 2.5c0 0 1.5 2.5 2.5 3.7 1 1.3 2.6 2.9 2.6 2.9l3.6-3.6M6 38.9l-4.9 1.3M6 38.9l-4.9 1.3M6 38.9l-4.9 1.3c0 0 0.8 2.8 1.4 4.2 0.6 1.5 1.8 3.5 1.8 3.5l4.4-2.5" fill="#c00"/><path d="m5.1 32-5.1 0c0 0 0.1 2.9 0.3 4.5 0.2 1.6 0.8 3.8 0.8 3.8l4.9-1.3M6 25 1.1 23.7c0 0-0.7 2.8-0.9 4.4-0.2 1.6-0.2 3.9-0.2 3.9l5.1 0" fill="#ce5c00"/><path d="m8.7 18.5-4.4-2.6c0 0-1.4 2.5-2 4-0.6 1.5-1.2 3.7-1.2 3.7l4.9 1.3" fill="#729fcf"/></svg>
|
After Width: | Height: | Size: 2.6 KiB |
|
@ -119,6 +119,7 @@ exports.files = function () {
|
|||
'modules/admin/admin-submenu-roles',
|
||||
'modules/admin/admin-roles',
|
||||
'modules/admin/admin-functionalities',
|
||||
'modules/admin/admin-project-export',
|
||||
'modules/admin/admin-membership-table',
|
||||
'modules/admin/admin-project-profile',
|
||||
'modules/admin/default-values',
|
||||
|
|
Loading…
Reference in New Issue