diff --git a/fileutils/file.go b/fileutils/file.go index 1b1e6403..00549584 100644 --- a/fileutils/file.go +++ b/fileutils/file.go @@ -2,6 +2,7 @@ package fileutils import ( "io" + "os" "path/filepath" "github.com/spf13/afero" @@ -25,7 +26,7 @@ func CopyFile(fs afero.Fs, source, dest string) error { } // Create the destination file. - dst, err := fs.Create(dest) + dst, err := fs.OpenFile(dest, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0775) if err != nil { return err } diff --git a/frontend/public/themes/dark.css b/frontend/public/themes/dark.css index 978b4f0f..1ee5f1ac 100644 --- a/frontend/public/themes/dark.css +++ b/frontend/public/themes/dark.css @@ -1,7 +1,7 @@ :root { - --background: #121212; - --surfacePrimary: #171819; - --surfaceSecondary: #212528; + --background: #141D24; + --surfacePrimary: #20292F; + --surfaceSecondary: #3A4147; --divider: rgba(255, 255, 255, 0.12); --icon: #ffffff; --textPrimary: rgba(255, 255, 255, 0.87); @@ -16,7 +16,7 @@ body { #loading { background: var(--background); } -#loading .spinner div { +#loading .spinner div, #previewer .loading .spinner div { background: var(--icon); } @@ -30,25 +30,34 @@ header { #search #input { background: var(--surfaceSecondary); + border-color: var(--surfacePrimary); } -#search.active #input, -#search.active .boxes { +#search #input input::placeholder { + color: var(--textSecondary); +} +#search.active #input { background: var(--surfacePrimary); } #search.active input { color: var(--textPrimary); } -#search.active #result { +#search #result { background: var(--background); color: var(--textPrimary); } -#search.active .boxes h3 { +#search .boxes { + background: var(--surfaceSecondary); +} +#search .boxes h3 { color: var(--textPrimary); } .action { color: var(--textPrimary) !important; } +.action:hover { + background-color: rgba(255, 255, 255, .1); +} .action i { color: var(--icon) !important; } @@ -93,6 +102,10 @@ nav > div { background: var(--background); } +.message { + color: var(--textPrimary); +} + .card { background: var(--surfacePrimary); color: var(--textPrimary); @@ -106,9 +119,23 @@ nav > div { .dashboard p label { color: var(--textPrimary); } +.card#share ul li input, +.card#share ul li select, .input { background: var(--surfaceSecondary); color: var(--textPrimary); + border: 1px solid rgba(255, 255, 255, 0.05); +} +.input:hover, +.input:focus { + border-color: rgba(255, 255, 255, 0.15); +} +.input--red { + background: #73302D; +} + +.input--green { + background: #147A41; } .dashboard #nav li, @@ -119,10 +146,27 @@ nav > div { color: var(--textPrimary); } +table th { + color: var(--textSecondary); +} + +.file-list li:hover { + background: var(--surfaceSecondary); +} +.file-list li:before { + color: var(--textSecondary); +} +.file-list li[aria-selected=true]:before { + color: var(--icon); +} + .shell { background: var(--surfacePrimary); color: var(--textPrimary); } +.shell__result { + border-top: 1px solid var(--divider); +} #editor-container { background: var(--background); @@ -146,3 +190,11 @@ nav > div { background: var(--surfaceSecondary) !important; } } + +.share__box, .share__box__download { + background: var(--surfaceSecondary) !important; + color: var(--textPrimary); +} +.share__box__download { + border-bottom-color: var(--divider); +} \ No newline at end of file diff --git a/frontend/src/api/files.js b/frontend/src/api/files.js index 602090c1..5942e71a 100644 --- a/frontend/src/api/files.js +++ b/frontend/src/api/files.js @@ -94,9 +94,6 @@ export async function post (url, content = '', overwrite = false, onupload) { request.upload.onprogress = onupload } - // Send a message to user before closing the tab during file upload - window.onbeforeunload = () => "Files are being uploaded." - request.onload = () => { if (request.status === 200) { resolve(request.responseText) @@ -112,29 +109,28 @@ export async function post (url, content = '', overwrite = false, onupload) { } request.send(content) - // Upload is done no more message before closing the tab - }).finally(() => { window.onbeforeunload = null }) + }) } -function moveCopy (items, copy = false) { +function moveCopy (items, copy = false, overwrite = false, rename = false) { let promises = [] for (let item of items) { const from = removePrefix(item.from) const to = encodeURIComponent(removePrefix(item.to)) - const url = `${from}?action=${copy ? 'copy' : 'rename'}&destination=${to}` + const url = `${from}?action=${copy ? 'copy' : 'rename'}&destination=${to}&override=${overwrite}&rename=${rename}` promises.push(resourceAction(url, 'PATCH')) } return Promise.all(promises) } -export function move (items) { - return moveCopy(items) +export function move (items, overwrite = false, rename = false) { + return moveCopy(items, false, overwrite, rename) } -export function copy (items) { - return moveCopy(items, true) +export function copy (items, overwrite = false, rename = false) { + return moveCopy(items, true, overwrite, rename) } export async function checksum (url, algo) { diff --git a/frontend/src/components/files/ExtendedImage.vue b/frontend/src/components/files/ExtendedImage.vue index 2fa0f35e..aeb27981 100644 --- a/frontend/src/components/files/ExtendedImage.vue +++ b/frontend/src/components/files/ExtendedImage.vue @@ -10,10 +10,12 @@ @mouseup="mouseUp" @wheel="wheelMove" > - + diff --git a/frontend/src/css/_buttons.css b/frontend/src/css/_buttons.css index 79dab790..087c6286 100644 --- a/frontend/src/css/_buttons.css +++ b/frontend/src/css/_buttons.css @@ -25,8 +25,8 @@ background: var(--red); } -.button--red:hover { - background: var(--dark-red); +.button--blue { + background: var(--blue); } .button--flat { diff --git a/frontend/src/css/styles.css b/frontend/src/css/styles.css index 2da1d242..8d486c0d 100644 --- a/frontend/src/css/styles.css +++ b/frontend/src/css/styles.css @@ -125,8 +125,13 @@ height: 3.7em; } -#previewer .action:first-of-type { +#previewer .bar .title { margin-right: auto; + padding: 0 1em; + line-height: 2.7em; + overflow: hidden; + word-break: break-word; + color: #fff; } #previewer .action i { @@ -219,6 +224,11 @@ font-size: 1.2em; } +#previewer .loading { + height: 100%; + width: 100%; +} + #editor-container #editor { height: calc(100vh - 8.2em); } diff --git a/frontend/src/store/index.js b/frontend/src/store/index.js index 3662d3a6..40b56df4 100644 --- a/frontend/src/store/index.js +++ b/frontend/src/store/index.js @@ -23,7 +23,8 @@ const state = { show: null, showShell: false, showMessage: null, - showConfirm: null + showConfirm: null, + previewMode: false } export default new Vuex.Store({ diff --git a/frontend/src/store/modules/upload.js b/frontend/src/store/modules/upload.js index 738edaf9..6552dd01 100644 --- a/frontend/src/store/modules/upload.js +++ b/frontend/src/store/modules/upload.js @@ -37,6 +37,11 @@ const mutations = { } } +const beforeUnload = (event) => { + event.preventDefault() + event.returnValue = '' +} + const actions = { upload: (context, item) => { let uploadsCount = Object.keys(context.state.uploads).length; @@ -45,6 +50,7 @@ const actions = { let isUploadsEmpty = uploadsCount == 0 if (isQueueEmpty && isUploadsEmpty) { + window.addEventListener('beforeunload', beforeUnload) buttons.loading('upload') } @@ -67,6 +73,7 @@ const actions = { let canProcess = isBellowLimit && !isQueueEmpty if (isFinished) { + window.removeEventListener('beforeunload', beforeUnload) buttons.success('upload') context.commit('reset') context.commit('setReload', true, { root: true }) diff --git a/frontend/src/store/mutations.js b/frontend/src/store/mutations.js index 579edbec..46aad6e6 100644 --- a/frontend/src/store/mutations.js +++ b/frontend/src/store/mutations.js @@ -78,10 +78,14 @@ const mutations = { updateClipboard: (state, value) => { state.clipboard.key = value.key state.clipboard.items = value.items + state.clipboard.path = value.path }, resetClipboard: (state) => { state.clipboard.key = '' state.clipboard.items = [] + }, + setPreviewMode(state, value) { + state.previewMode = value } } diff --git a/frontend/src/utils/upload.js b/frontend/src/utils/upload.js index 61e6a8e7..54b5669e 100644 --- a/frontend/src/utils/upload.js +++ b/frontend/src/utils/upload.js @@ -6,10 +6,7 @@ export function checkConflict(files, items) { items = [] } - let folder_upload = false - if (files[0].fullPath !== undefined) { - folder_upload = true - } + let folder_upload = files[0].fullPath !== undefined let conflict = false for (let i = 0; i < files.length; i++) { @@ -69,7 +66,7 @@ export function scanFiles(dt) { const dir = { isDir: true, size: 0, - path: `${directory}${entry.name}` + fullPath: `${directory}${entry.name}` } contents.push(dir) @@ -112,7 +109,7 @@ export function handleFiles(files, path, overwrite = false) { if (file.isDir) { itemPath = path - let folders = file.path.split("/") + let folders = file.fullPath.split("/") for (let i = 0; i < folders.length; i++) { let folder = folders[i] diff --git a/frontend/src/views/Files.vue b/frontend/src/views/Files.vue index af54a91c..c0e03bfc 100644 --- a/frontend/src/views/Files.vue +++ b/frontend/src/views/Files.vue @@ -10,14 +10,15 @@ {{ link.name }} +
+ -

{{ $t('files.loading') }} @@ -65,7 +66,7 @@ export default { 'show' ]), isPreview () { - return !this.loading && !this.isListing && !this.isEditor + return !this.loading && !this.isListing && !this.isEditor || this.loading && this.$store.state.previewMode }, breadcrumbs () { let parts = this.$route.path.split('/') diff --git a/http/resource.go b/http/resource.go index 95224083..20376968 100644 --- a/http/resource.go +++ b/http/resource.go @@ -127,6 +127,10 @@ var resourcePostPutHandler = withUser(func(w http.ResponseWriter, r *http.Reques return nil }, action, r.URL.Path, "", d.user) + if err != nil { + _ = d.user.Fs.RemoveAll(r.URL.Path) + } + return errToStatus(err), err }) @@ -144,6 +148,31 @@ var resourcePatchHandler = withUser(func(w http.ResponseWriter, r *http.Request, return http.StatusForbidden, nil } + override := r.URL.Query().Get("override") == "true" + rename := r.URL.Query().Get("rename") == "true" + + if !override && !rename { + if _, err = d.user.Fs.Stat(dst); err == nil { + return http.StatusConflict, nil + } + } + + if rename { + counter := 1 + dir, name := filepath.Split(dst) + ext := filepath.Ext(name) + base := strings.TrimSuffix(name, ext) + + for { + if _, err = d.user.Fs.Stat(dst); err != nil { + break + } + new := fmt.Sprintf("%s(%d)%s", base, counter, ext) + dst = filepath.Join(dir, new) + counter++ + } + } + err = d.RunHook(func() error { switch action { // TODO: use enum