From 8b1bcff230843bdb5856efd55865fd29dd6c3694 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 20 May 2017 18:35:26 +0200 Subject: [PATCH] switch from polymer to materialize fixes #3 --- assets/scan2drive.js | 84 ++--- bundle.go | 2 +- internal/bundled/GENERATED_bundled.go | 8 +- templates/Index.html.tmpl | 326 +++++++++--------- templates/Setup.html.tmpl | 141 +++----- templates/templates.go | 467 +++++++++++--------------- 6 files changed, 455 insertions(+), 573 deletions(-) diff --git a/assets/scan2drive.js b/assets/scan2drive.js index 654b595..c25582d 100644 --- a/assets/scan2drive.js +++ b/assets/scan2drive.js @@ -3,14 +3,18 @@ // https://developers.google.com/identity/sign-in/web/reference // https://developers.google.com/picker/docs/reference -var user; +function httpErrorToToast(jqXHR, prefix) { + var summary = 'HTTP ' + jqXHR.status + ': ' + jqXHR.responseText; + Materialize.toast(prefix + ': ' + summary, 5000, 'red'); + console.log('error: prefix', prefix, ', summary', summary); +} +// start is called once the Google APIs were loaded function start() { console.log('start'); gapi.load('auth2', function() { - // TODO: avoid a global variable here. - auth2 = gapi.auth2.init({ + var auth2 = gapi.auth2.init({ client_id: clientID, // The “profile” and “email” scope are always requested. scope: 'https://www.googleapis.com/auth/drive', @@ -22,14 +26,17 @@ function start() { // browser, but not on the server side (e.g. because sessions were // deleted). if (auth2.isSignedIn.get() && user.getId() === sub) { + console.log('logged in'); $('#user-avatar').attr('src', user.getBasicProfile().getImageUrl()); $('#user-name').text(user.getBasicProfile().getName()); - $('paper-fab').show(); + $('.fixed-action-btn').show(); $('#signin').hide(); $('#signout').show(); $('#settings-button').show(); // TODO: open settings button in case drive folder is not configured - } + } else { + console.log('auth2 loaded, but user not logged in'); + } }, function(err) { var errorp = $('#error p'); errorp.text('Error ' + err.error + ': ' + err.details); @@ -37,9 +44,20 @@ function start() { }); }); + gapi.signin2.render('my-signin2', { + 'scope': 'profile email', + 'width': 240, + 'height': 50, + 'longtitle': true, + 'theme': 'dark', + 'onsuccess': function(){ console.log('success'); }, + 'onfailure': function() { console.log('failure'); } + }); + gapi.load('picker', function() {}); $('#signinButton').click(function() { + var auth2 = gapi.auth2.getAuthInstance(); auth2.grantOfflineAccess({'redirect_uri': 'postmessage'}).then(signInCallback); }); @@ -61,6 +79,7 @@ function start() { // TODO: #signin keypress $('#signin').click(function(ev) { + var auth2 = gapi.auth2.getAuthInstance(); auth2.grantOfflineAccess({'redirect_uri': 'postmessage'}).then(signInCallback); ev.preventDefault(); }); @@ -71,6 +90,7 @@ function start() { } function pollScan(name) { + var user = gapi.auth2.getAuthInstance().currentUser.get(); $.ajax({ type: 'POST', url: '/scanstatus', @@ -83,8 +103,9 @@ function pollScan(name) { return; } if (data.Done) { - $('#scan-dialog paper-spinner-lite').attr('active', null); - $('paper-fab').attr('icon', 'hardware:scanner').attr('disabled', null); + $('#scan-dialog paper-spinner-lite').attr('active', null); // TODO + $('.fixed-action-btn i').text('scanner'); + $('.fixed-action-btn a').removeClass('disabled'); $('#scan-dialog').off('iron-overlay-canceled'); var sub = user.getBasicProfile().getId(); $('#scan-dialog .scan-thumb').css('background', 'url("scans_dir/' + sub + '/' + name + '/thumb.png")').css('background-size', 'cover'); @@ -118,7 +139,7 @@ function renameScan(name, newSuffix) { $('#scan-form paper-input iron-icon').show(); }, error: function(jqXHR, textStatus, errorThrown) { - console.log(textStatus); + httpErrorToToast(jqXHR, 'renaming scan failed'); }, processData: false, data: JSON.stringify({ @@ -130,13 +151,9 @@ function renameScan(name, newSuffix) { function scan() { // Only one scan can be in progress at a time. - $('paper-fab').attr('icon', 'hourglass-empty').attr('disabled', true); - - document.getElementById('scan-dialog').open(); - $('#scan-dialog').on('iron-overlay-canceled', function(ev) { - // TODO: move status to toolbar instead of blocking close - ev.preventDefault(); - }); + $('.fixed-action-btn i').text('hourglass_empty'); + $('.fixed-action-btn a').addClass('disabled'); + $('#scan-dialog').modal('open'); $.ajax({ type: 'POST', @@ -150,27 +167,10 @@ function scan() { pollScan(data.Name); }, error: function(jqXHR, textStatus, errorThrown) { - var exitStatus = jqXHR.getResponseHeader('X-Exit-Status'); - if (exitStatus === undefined) { - console.log('TODO: error handler for non-scanimage internal server error'); - return; - } - // From sane-backends-1.0.25/include/sane/sane.h - // SANE_STATUS_GOOD = 0, /* everything A-OK */ - // SANE_STATUS_UNSUPPORTED, /* operation is not supported */ - // SANE_STATUS_CANCELLED, /* operation was cancelled */ - // SANE_STATUS_DEVICE_BUSY, /* device is busy; try again later */ - // SANE_STATUS_INVAL, /* data is invalid (includes no dev at open) */ - // SANE_STATUS_EOF, /* no more data available (end-of-file) */ - // SANE_STATUS_JAMMED, /* document feeder jammed */ - // SANE_STATUS_NO_DOCS, /* document feeder out of documents */ - // SANE_STATUS_COVER_OPEN, /* scanner cover is open */ - // SANE_STATUS_IO_ERROR, /* error during device I/O */ - // SANE_STATUS_NO_MEM, /* out of memory */ - // SANE_STATUS_ACCESS_DENIED /* access to resource has been denied */ - - // TODO: butter bar with human-readable error code - console.log('error ', exitStatus); + $('#scan-dialog').modal('close'); + $('.fixed-action-btn i').text('scanner'); + $('.fixed-action-btn a').removeClass('disabled'); + httpErrorToToast(jqXHR, 'scanning failed'); }, }); } @@ -192,12 +192,10 @@ function pickerCallback(data) { url: '/storedrivefolder', contentType: 'application/json', success: function(data, textStatus, jqXHR) { - console.log('result', data, 'textStatus', textStatus, 'jqXHR', jqXHR); - if (jqXHR.status !== 200) { - // TODO: show error message - return; - } - $('#drive-folder').html($('').attr('src', picked.iconUrl).css('margin-right', '0.25em')).append($('').attr('href', picked.url).text(picked.name)); + $('#drivefolder').val(picked.name); + }, + error: function(jqXHR, textStatus, errorThrown) { + httpErrorToToast(jqXHR, 'storing drive folder failed'); }, data: JSON.stringify({ 'Id': picked.id, @@ -209,6 +207,8 @@ function pickerCallback(data) { } function createPicker() { + var user = gapi.auth2.getAuthInstance().currentUser.get(); + if (!user) { // The picker requires an OAuth token. return; diff --git a/bundle.go b/bundle.go index 710fb3c..6d24965 100644 --- a/bundle.go +++ b/bundle.go @@ -1,3 +1,3 @@ package main -//go:generate sh -c "go run goembed.go -package bundled -var assets assets/elements.vulcanized.html assets/scan2drive.js assets/drive48.png > internal/bundled/GENERATED_bundled.go" +//go:generate sh -c "go run goembed.go -package bundled -var assets assets/scan2drive.js > internal/bundled/GENERATED_bundled.go" diff --git a/internal/bundled/GENERATED_bundled.go b/internal/bundled/GENERATED_bundled.go index 9ddc88c..a2f013d 100644 --- a/internal/bundled/GENERATED_bundled.go +++ b/internal/bundled/GENERATED_bundled.go @@ -2,10 +2,6 @@ package bundled // Table of contents var assets = map[string][]byte{ - "assets/elements.vulcanized.html": assets_0, - "assets/scan2drive.js": assets_1, - "assets/drive48.png": assets_2, + "assets/scan2drive.js": assets_0, } -var assets_0 = []byte("\n") -var assets_1 = []byte("// vim:ts=4:sw=4:et\n// Documentation references:\n// https://developers.google.com/identity/sign-in/web/reference\n// https://developers.google.com/picker/docs/reference\n\nvar user;\n\nfunction start() {\n console.log('start');\n\n gapi.load('auth2', function() {\n // TODO: avoid a global variable here.\n auth2 = gapi.auth2.init({\n client_id: clientID,\n // The “profile” and “email” scope are always requested.\n scope: 'https://www.googleapis.com/auth/drive',\n });\n auth2.then(function() {\n user = auth2.currentUser.get();\n var sub = $('#user-name').attr('data-sub');\n // If sub does not match the user id, we are logged in in the\n // browser, but not on the server side (e.g. because sessions were\n // deleted).\n if (auth2.isSignedIn.get() && user.getId() === sub) {\n $('#user-avatar').attr('src', user.getBasicProfile().getImageUrl());\n $('#user-name').text(user.getBasicProfile().getName());\n $('paper-fab').show();\n $('#signin').hide();\n $('#signout').show();\n $('#settings-button').show();\n // TODO: open settings button in case drive folder is not configured\n }\n }, function(err) {\n var errorp = $('#error p');\n errorp.text('Error ' + err.error + ': ' + err.details);\n console.log('OAuth2 error', err);\n });\n });\n\n gapi.load('picker', function() {});\n\n $('#signinButton').click(function() {\n auth2.grantOfflineAccess({'redirect_uri': 'postmessage'}).then(signInCallback);\n });\n\n $('#signout').click(function(ev) {\n // TODO: it seems like disconnect() won’t actually revoke scopes\n // sometimes, perhaps when being called to soon after authorizing?\n auth2.disconnect();\n $.ajax({\n type: 'POST',\n url: '/signout',\n success: function(result) {\n // Reload the page.\n window.location.href = window.location.href;\n },\n // TODO: error handling (deleting file failed, e.g. because of readonly file system)\n });\n ev.preventDefault();\n });\n\n // TODO: #signin keypress\n $('#signin').click(function(ev) {\n auth2.grantOfflineAccess({'redirect_uri': 'postmessage'}).then(signInCallback);\n ev.preventDefault();\n });\n\n $('#select-drive-folder').click(function() {\n createPicker();\n });\n}\n\nfunction pollScan(name) {\n $.ajax({\n type: 'POST',\n url: '/scanstatus',\n contentType: 'application/json',\n success: function(data, textStatus, jqXHR) {\n $('#scan-progress-status').text(data.Status);\n console.log('result', data, 'textStatus', textStatus, 'jqXHR', jqXHR);\n if (jqXHR.status !== 200) {\n // TODO: show error message\n return;\n }\n if (data.Done) {\n $('#scan-dialog paper-spinner-lite').attr('active', null);\n $('paper-fab').attr('icon', 'hardware:scanner').attr('disabled', null);\n $('#scan-dialog').off('iron-overlay-canceled');\n var sub = user.getBasicProfile().getId();\n $('#scan-dialog .scan-thumb').css('background', 'url(\"scans_dir/' + sub + '/' + name + '/thumb.png\")').css('background-size', 'cover');\n } else {\n setTimeout(function() { pollScan(name); }, 500);\n }\n },\n error: function(jqXHR, textStatus, errorThrown) {\n if (jqXHR.status === 404) {\n // Scan was not yet found because the directory rescan isn’t done.\n // Retry in a little while.\n setTimeout(function() { pollScan(name); }, 500);\n } else {\n $('#scan-progress-status').text('Error: ' + errorThrown);\n setTimeout(function() { pollScan(name); }, 500);\n }\n },\n processData: false,\n data: JSON.stringify({'Name':name}),\n });\n}\n\nfunction renameScan(name, newSuffix) {\n var newName = name + '-' + newSuffix;\n\n $.ajax({\n type: 'POST',\n url: '/renamescan',\n contentType: 'application/json',\n success: function(data, textStatus, jqXHR) {\n $('#scan-form paper-input iron-icon').show();\n },\n error: function(jqXHR, textStatus, errorThrown) {\n console.log(textStatus);\n },\n processData: false,\n data: JSON.stringify({\n Name: name,\n NewName: newName,\n }),\n });\n}\n\nfunction scan() {\n // Only one scan can be in progress at a time.\n $('paper-fab').attr('icon', 'hourglass-empty').attr('disabled', true);\n\n document.getElementById('scan-dialog').open();\n $('#scan-dialog').on('iron-overlay-canceled', function(ev) {\n // TODO: move status to toolbar instead of blocking close\n ev.preventDefault();\n });\n\n $.ajax({\n type: 'POST',\n url: '/startscan',\n success: function(data, textStatus, jqXHR) {\n $('#scan-dialog paper-input[name=\"name\"] div[prefix]').text(data.Name + '-');\n var renameButton = $('#scan-form paper-button');\n renameButton.click(function(ev) {\n renameScan(data.Name, $('#scan-form paper-input').val());\n });\n pollScan(data.Name);\n },\n error: function(jqXHR, textStatus, errorThrown) {\n var exitStatus = jqXHR.getResponseHeader('X-Exit-Status');\n if (exitStatus === undefined) {\n console.log('TODO: error handler for non-scanimage internal server error');\n return;\n }\n // From sane-backends-1.0.25/include/sane/sane.h\n // SANE_STATUS_GOOD = 0, /* everything A-OK */\n // SANE_STATUS_UNSUPPORTED, /* operation is not supported */\n // SANE_STATUS_CANCELLED, /* operation was cancelled */\n // SANE_STATUS_DEVICE_BUSY, /* device is busy; try again later */\n // SANE_STATUS_INVAL, /* data is invalid (includes no dev at open) */\n // SANE_STATUS_EOF, /* no more data available (end-of-file) */\n // SANE_STATUS_JAMMED, /* document feeder jammed */\n // SANE_STATUS_NO_DOCS, /* document feeder out of documents */\n // SANE_STATUS_COVER_OPEN, /* scanner cover is open */\n // SANE_STATUS_IO_ERROR, /* error during device I/O */\n // SANE_STATUS_NO_MEM, /* out of memory */\n // SANE_STATUS_ACCESS_DENIED /* access to resource has been denied */\n\n // TODO: butter bar with human-readable error code\n console.log('error ', exitStatus);\n },\n });\n}\n\n// callback has “loaded”, “cancel” and “picked”\nfunction pickerCallback(data) {\n console.log('picker callback', data);\n if (data.action !== google.picker.Action.PICKED) {\n return;\n }\n if (data.docs.length !== 1) {\n // TODO: error handling\n return;\n }\n var picked = data.docs[0];\n // TODO: show a spinner\n $.ajax({\n type: 'POST',\n url: '/storedrivefolder',\n contentType: 'application/json',\n success: function(data, textStatus, jqXHR) {\n console.log('result', data, 'textStatus', textStatus, 'jqXHR', jqXHR);\n if (jqXHR.status !== 200) {\n // TODO: show error message\n return;\n }\n $('#drive-folder').html($('').attr('src', picked.iconUrl).css('margin-right', '0.25em')).append($('').attr('href', picked.url).text(picked.name));\n },\n data: JSON.stringify({\n 'Id': picked.id,\n 'IconUrl': picked.iconUrl,\n 'Url': picked.url,\n 'Name': picked.name,\n }),\n });\n}\n\nfunction createPicker() {\n if (!user) {\n // The picker requires an OAuth token.\n return;\n }\n\n var docsView = new google.picker.DocsView()\n .setIncludeFolders(true)\n .setMimeTypes('application/vnd.google-apps.folder')\n .setMode(google.picker.DocsViewMode.LIST)\n .setSelectFolderEnabled(true);\n\n var picker = new google.picker.PickerBuilder()\n .addView(docsView)\n .setCallback(pickerCallback)\n .setOAuthToken(user.getAuthResponse().access_token)\n .build();\n picker.setVisible(true);\n}\n\nfunction signInCallback(authResult) {\n if (authResult['code']) {\n // TODO: progress indicator, writing to disk and examining scans could take a while.\n $.ajax({\n type: 'POST',\n url: '/oauth',\n contentType: 'application/octet-stream; charset=utf-8',\n success: function(result) {\n // Reload the page.\n window.location.href = window.location.href;\n },\n error: function(jqXHR, textStatus, errorThrown) {\n if (jqXHR.status == 500) {\n $('#error p').text('OAuth error: ' + jqXHR.responseText + '. Try revoking access on https://security.google.com/settings/u/0/security/permissions, then retry');\n } else {\n $('#error p').text('Unknown OAuth error: ' + errorThrown);\n }\n },\n processData: false,\n data: authResult['code'],\n });\n } else {\n console.log('sth went wrong :|', authResult);\n // TODO: trigger logout, without server-side auth we are screwed\n }\n}\n") -var assets_2 = []byte("\x89PNG \n\n\x00\x00\x00 IHDR\x00\x00\x000\x00\x00\x000\x00\x00\x00W\xf9\x87\x00\x00\x00tEXtSoftware\x00Adobe ImageReadyq\xc9e<\x00\x00hiTXtXML:com.adobe.xmp\x00\x00\x00\x00\x00 Zc\xaf\xec\x00\x00\xbfIDATx\xda\xecZ;n\xc2@e\x8d\xa3Pr\xb8t)\xa1K:\xb8\x81]E\xe9\xe0\x84\x00%t)\xe1\xa6L70\xe9R\xba\xe5Cf\xa3A\"\xe0\xcf23\xb6\x85\xc2H+\xf31\xeb\xf9\xbd\xd97#\xd4n\xb7+\\\xb2X\x85 \x97\xaby\x8b\xe2\xfc\xf8\xe6\xb6T\x86\x8b\xab\xcc\xd8f\xf8\xb1\xddt37\x00\x94\xaf\xc1\xa5\x82k\xc0td\x8cXgm\x80\x87\xca\xd7a-\xf15U`@33 \x80\xf2\\\xa8t\x96ˌ@\xf6le\xcc\xfbC\x8f\x9dF-\x86k\x88B5\x8bt\x8eҥ\x8c\xcaw\x99Q\xa8\x80s\x9eS\x8d\x00<\xa0\x82\xde\xab:ML\xabÈ\x00\xa4\x81AL\xc9\xd4\xdf Q \xaa\x94ϭh\xea \xefk\xefz \xb7\xed\xc1 +scan2drive - - + + + + + + + - - scan2drive - - -
-
- Not logged in
- + + + + - -

Settings

- -
- - -
- select -

- {{ if eq .user.Folder.Id "" }} - No drive folder selected - {{ else }} - - {{ .user.Folder.Name }} - {{ end }} -

-
- close + - -
-

Scan

-
- - Scanning pages… +
+
+

+ scan2drive’s purpose is to store scans on Google Drive, so please +

+

+
+
+

-
- -
- -
- Rename scan -
- -
- + +
+
-
-
-

-scan2drive’s purpose is to store scans on Google Drive, so please -

-
-
-

-
{{ range $key := .keys }} {{ with $scan := index $.scans $key }} {{ if eq $scan.Sub $.sub }} - -
-
-

+

+
+
+
+
+ {{ if eq $scan.NewName "" }} {{ $key }} {{ else }} {{ $key }}-{{ $scan.NewName }} {{ end }} -

-
- {{ if (not $scan.Converted) }} - - queued for conversion - {{ else if (or (not $scan.UploadedOriginals) (not $scan.UploadedPDF)) }} - - uploading - {{ else }} - - done - {{ end }} -
-
-
- view in drive -
-
-
- + +

+ {{ if (not $scan.Converted) }} + cloud_queue queued for conversion + {{ else if (or (not $scan.UploadedOriginals) (not $scan.UploadedPDF)) }} + cloud_upload uploading + {{ else }} + cloud_done done + {{ end }} + +

+
+ +
+
+ +
+
+
+
{{ end }} {{ end }} {{ end }} -
- - +
+
+ + + + + diff --git a/templates/Setup.html.tmpl b/templates/Setup.html.tmpl index e33e6f1..9d740d7 100644 --- a/templates/Setup.html.tmpl +++ b/templates/Setup.html.tmpl @@ -6,122 +6,54 @@ - - - + + + + - - - - - scan2drive - - -
+ +
+ + +
+
-
-

Initial Setup

+
+
+ +

Initial Setup

You’re seeing this page because the OAuth client secret could not be read from {{ .ClientSecretPath }} (configured via the -client_secret_path flag).

@@ -150,8 +82,11 @@ You’re seeing this page because the OAuth client secret could not be read from
  • Avail the downloaded JSON credential file as {{ .ClientSecretPath }}.
  • {{ end }} -
    -
    - - + +
    +
    +
    +
    + + diff --git a/templates/templates.go b/templates/templates.go index b3029f3..5f8d3dc 100644 --- a/templates/templates.go +++ b/templates/templates.go @@ -10,12 +10,36 @@ import ( var IndexTpl = template.Must(template.New("Index").Parse(` +scan2drive - - + + + + + + + - + + - - scan2drive - - -
    -
    - Not logged in
    - + - -

    Settings

    - -
    - - -
    - select -

    - {{ if eq .user.Folder.Id "" }} - No drive folder selected - {{ else }} - - {{ .user.Folder.Name }} - {{ end }} -

    -
    - close + - -
    -

    Scan

    -
    - - Scanning pages… +
    +
    +

    + scan2drive’s purpose is to store scans on Google Drive, so please +

    +

    +
    +
    +

    -
    - -
    - -
    - Rename scan -
    - -
    - + +
    +
    -
    -
    -

    -scan2drive’s purpose is to store scans on Google Drive, so please -

    -
    -
    -

    -
    {{ range $key := .keys }} {{ with $scan := index $.scans $key }} {{ if eq $scan.Sub $.sub }} - -
    -
    -

    +

    +
    +
    +
    +
    + {{ if eq $scan.NewName "" }} {{ $key }} {{ else }} {{ $key }}-{{ $scan.NewName }} {{ end }} -

    -
    - {{ if (not $scan.Converted) }} - - queued for conversion - {{ else if (or (not $scan.UploadedOriginals) (not $scan.UploadedPDF)) }} - - uploading - {{ else }} - - done - {{ end }} -
    -
    -
    - view in drive -
    -
    -
    - + +

    + {{ if (not $scan.Converted) }} + cloud_queue queued for conversion + {{ else if (or (not $scan.UploadedOriginals) (not $scan.UploadedPDF)) }} + cloud_upload uploading + {{ else }} + cloud_done done + {{ end }} + +

    +
    + +
    +
    + +
    +
    +
    +
    {{ end }} {{ end }} {{ end }} -
    - - +
    +
    + + + + + `)) @@ -230,122 +238,54 @@ var SetupTpl = template.Must(template.New("Setup").Parse(` - - - + + + + - - - - - scan2drive - - -
    + +
    + + +
    +
    -
    -

    Initial Setup

    +
    +
    + +

    Initial Setup

    You’re seeing this page because the OAuth client secret could not be read from {{ .ClientSecretPath }} (configured via the -client_secret_path flag).

    @@ -374,9 +314,12 @@ You’re seeing this page because the OAuth client secret could not be read from
  • Avail the downloaded JSON credential file as {{ .ClientSecretPath }}.
  • {{ end }} -
    -
    - - + +
    +
    +
    +
    + + `))