ソースを参照

Added theme editor

Dmitriy Slipak 4 年 前
コミット
67e7a879f4
共有9 個のファイルを変更した276 個の追加241 個の削除を含む
  1. 4 0
      client/audio.js
  2. 5 95
      client/index.js
  3. 119 0
      client/theme-editor.js
  4. 4 0
      client/video.js
  5. 5 21
      editor/index.html
  6. 89 5
      editor/theme.html
  7. 49 5
      server/index.js
  8. BIN
      settings/backgrounds/nyp.png
  9. 1 115
      settings/themes.json

+ 4 - 0
client/audio.js ファイルの表示

@@ -44,6 +44,10 @@ function restart() {
44 44
 
45 45
 function update() {
46 46
 
47
+  if (!audio) {
48
+    return;
49
+  }
50
+
47 51
   if (audio.duration) {
48 52
 
49 53
     var pos = audio.currentTime / audio.duration;

+ 5 - 95
client/index.js ファイルの表示

@@ -3,7 +3,7 @@ var d3 = require("d3"),
3 3
     preview = require("./preview.js"),
4 4
     video = require("./video.js"),
5 5
     audio = require("./audio.js"),
6
-    newTheme = null,
6
+    editorEditor = require("./theme-editor.js"),
7 7
     subtitle = null;
8 8
 
9 9
 d3.json("/settings/themes.json", function(err, themes){
@@ -35,6 +35,10 @@ d3.json("/settings/themes.json", function(err, themes){
35 35
 
36 36
   preloadImages(themes);
37 37
 
38
+  if (location.pathname === '/theme') {
39
+    editorEditor.initialize();
40
+  }
41
+
38 42
 });
39 43
 
40 44
 function submitted() {
@@ -98,73 +102,6 @@ function submitted() {
98 102
 
99 103
 }
100 104
 
101
-function uploadTheme() {
102
-  var formData = new FormData();
103
-  var file = preview.newTheme();
104
-
105
-  formData.append("newTheme", file);
106
-
107
-  var newCaption = preview.newCaption();
108
-
109
-  formData.append("newCaption", newCaption);
110
-
111
-  $.ajax({
112
-    url: "/theme/upload",
113
-    type: "POST",
114
-    data: formData,
115
-    contentType: false,
116
-    cache: false,
117
-    processData: false,
118
-    success: function () {
119
-      d3.json("/settings/themes.json", function(err, themes){
120
-
121
-        var errorMessage;
122
-
123
-        // Themes are missing or invalid
124
-        if (err || !d3.keys(themes).filter(function(d){ return d !== "default"; }).length) {
125
-          if (err instanceof SyntaxError) {
126
-            errorMessage = "Error in settings/themes.json:<br/><code>" + err.toString() + "</code>";
127
-          } else if (err instanceof ProgressEvent) {
128
-            errorMessage = "Error: no settings/themes.json.";
129
-          } else if (err) {
130
-            errorMessage = "Error: couldn't load settings/themes.json.";
131
-          } else {
132
-            errorMessage = "No themes found in settings/themes.json.";
133
-          }
134
-          d3.select("#loading-bars").remove();
135
-          d3.select("#loading-message").html(errorMessage);
136
-          if (err) {
137
-            throw err;
138
-          }
139
-          return;
140
-        }
141
-
142
-        for (var key in themes) {
143
-          themes[key] = $.extend({}, themes.default, themes[key]);
144
-        }
145
-
146
-        preloadImages(themes);
147
-
148
-        d3.select("#input-theme")
149
-          .selectAll("option")
150
-          .each(function (d) {
151
-            if (d.name === newCaption) {
152
-              this["selected"] = "selected";
153
-              d3.select("#input-new-theme").property("value", "");
154
-              d3.select("#input-new-caption").property("value", "");
155
-              return;
156
-            }
157
-          });
158
-
159
-      });
160
-    },
161
-    error: function (error) {
162
-      console.log('error', error);
163
-    }
164
-  });
165
-
166
-}
167
-
168 105
 function poll(id) {
169 106
 
170 107
   setTimeout(function(){
@@ -257,12 +194,6 @@ function initialize(err, themesWithImages) {
257 194
     setClass(null);
258 195
   });
259 196
 
260
-  d3.select("#btn-new-theme").on("click", uploadTheme);
261
-
262
-  d3.select("#input-new-theme").on("change", updateNewThemeFile).each(updateNewThemeFile);
263
-
264
-  d3.select("#input-new-caption").on("change keyup", updateNewCaption).each(updateNewCaption);
265
-
266 197
   d3.select("#input-subtitle").on("change", updateSubtitleFile).each(updateSubtitleFile);
267 198
 
268 199
   d3.select("#submit").on("click", submitted);
@@ -305,23 +236,6 @@ function updateAudioFile() {
305 236
 
306 237
 }
307 238
 
308
-function updateNewThemeFile() {
309
-  if (!this.files || !this.files[0]) {
310
-    preview.newTheme(null);
311
-    setClass(null);
312
-    return true;
313
-  }
314
-
315
-  newTheme = this.files[0];
316
-  preview.loadNewTheme(newTheme, function (err) {
317
-    if (err) {
318
-      setClass("error", "Error updating new theme file");
319
-    } else {
320
-      setClass(null);
321
-    }
322
-  });
323
-}
324
-
325 239
 function updateSubtitleFile() {
326 240
   if (!this.files || !this.files[0]) {
327 241
     preview.subtitle(null);
@@ -347,10 +261,6 @@ function updateTheme() {
347 261
   preview.theme(d3.select(this.options[this.selectedIndex]).datum());
348 262
 }
349 263
 
350
-function updateNewCaption() {
351
-  preview.newCaption(this.value);
352
-}
353
-
354 264
 function preloadImages(themes) {
355 265
 
356 266
   // preload images

+ 119 - 0
client/theme-editor.js ファイルの表示

@@ -0,0 +1,119 @@
1
+var d3 = require("d3"),
2
+    $ = require("jquery"),
3
+    preview = require("./preview.js");
4
+
5
+function _initialize() {
6
+    d3.select("#btn-new-theme").on("click", uploadTheme);
7
+  	d3.select("#input-new-theme").on("change", updateNewThemeFile).each(updateNewThemeFile);
8
+  	d3.select("#input-new-caption").on("change keyup", updateNewCaption).each(updateNewCaption);
9
+  	d3.select("#btn-delete-theme").on("click", deleteTheme);
10
+}
11
+
12
+function setClass(cl, msg) {
13
+  d3.select("body").attr("class", cl || null);
14
+  d3.select("#error").text(msg || "");
15
+}
16
+
17
+function uploadTheme() {
18
+  var formData = new FormData();
19
+  var file = preview.newTheme();
20
+
21
+  formData.append("newTheme", file);
22
+
23
+  var newCaption = preview.newCaption();
24
+
25
+  formData.append("newCaption", newCaption);
26
+
27
+  $.ajax({
28
+    url: "/theme/upload",
29
+    type: "POST",
30
+    data: formData,
31
+    dataType: "json",
32
+    contentType: false,
33
+    cache: false,
34
+    processData: false,
35
+    success: function () {
36
+      d3.json("/settings/themes.json", function(err, themes){
37
+
38
+        var errorMessage;
39
+
40
+        // Themes are missing or invalid
41
+        if (err || !d3.keys(themes).filter(function(d){ return d !== "default"; }).length) {
42
+          if (err instanceof SyntaxError) {
43
+            errorMessage = "Error in settings/themes.json:<br/><code>" + err.toString() + "</code>";
44
+          } else if (err instanceof ProgressEvent) {
45
+            errorMessage = "Error: no settings/themes.json.";
46
+          } else if (err) {
47
+            errorMessage = "Error: couldn't load settings/themes.json.";
48
+          } else {
49
+            errorMessage = "No themes found in settings/themes.json.";
50
+          }
51
+          d3.select("#loading-bars").remove();
52
+          d3.select("#loading-message").html(errorMessage);
53
+          if (err) {
54
+            throw err;
55
+          }
56
+          return;
57
+        }
58
+
59
+        location.reload();
60
+
61
+      });
62
+    },
63
+    error: function (error) {
64
+      console.log('error', error);
65
+    }
66
+  });
67
+
68
+}
69
+
70
+function updateNewThemeFile() {
71
+  if (!this.files || !this.files[0]) {
72
+    preview.newTheme(null);
73
+    setClass(null);
74
+    return true;
75
+  }
76
+
77
+  newTheme = this.files[0];
78
+  preview.loadNewTheme(newTheme, function (err) {
79
+    if (err) {
80
+      setClass("error", "Error updating new theme file");
81
+    } else {
82
+      setClass(null);
83
+    }
84
+  });
85
+}
86
+
87
+function updateNewCaption() {
88
+  preview.newCaption(this.value);
89
+}
90
+
91
+function deleteTheme() {
92
+	if(!confirm($("#btn-delete-theme").data("confirm"))){
93
+      d3.event.stopImmediatePropagation();
94
+      d3.event.preventDefault();
95
+      return;
96
+  }
97
+
98
+	var theme = d3.select("#input-theme").property("value");
99
+
100
+	$.ajax({
101
+    url: "/theme/delete",
102
+    type: "POST",
103
+    data: JSON.stringify({theme: theme}),
104
+    dataType: "json",
105
+    contentType: "application/json",
106
+    cache: false,
107
+    success: function () {
108
+    	location.reload();
109
+    },
110
+    error: function (error) {
111
+    	console.log('error', error);
112
+  	}
113
+	});
114
+
115
+}
116
+
117
+module.exports = {
118
+	initialize: _initialize
119
+};

+ 4 - 0
client/video.js ファイルの表示

@@ -4,6 +4,10 @@ var video = document.querySelector("video");
4 4
 
5 5
 function kill() {
6 6
 
7
+  if (!video) {
8
+    return;
9
+  }
10
+
7 11
   // Pause the video if it's playing
8 12
   if (!video.paused && !video.ended && 0 < video.currentTime) {
9 13
     video.pause();

+ 5 - 21
editor/index.html ファイルの表示

@@ -3,6 +3,8 @@
3 3
   <head>
4 4
     <title>Audiogram</title>
5 5
     <meta charset="utf-8" />
6
+    <meta name="robots" content="noindex, nofollow" />
7
+    <meta name="googlebot" content="noindex, nofollow" />
6 8
     <meta name="viewport" content="width=device-width, initial-scale=1">
7 9
     <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css">
8 10
     <link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,300,600" rel="stylesheet" type="text/css">
@@ -13,6 +15,9 @@
13 15
   <body class="loading">
14 16
     <div class="container">
15 17
       <h1>Audiogram</h1>
18
+      <div>
19
+        <a href="/theme">Go to theme editor</a>
20
+      </div>
16 21
       <div id="loading">
17 22
         <div id="loading-bars">
18 23
           <div class="r1"></div><div class="r2"></div><div class="r3"></div><div class="r4"></div><div class="r5"></div>
@@ -42,27 +47,6 @@
42 47
           </label>
43 48
           <input id="input-caption" name="caption" type="text" autocomplete="off" placeholder="Add a caption" />
44 49
         </div>
45
-
46
-        <!--div class="row form-row">
47
-          <hr>
48
-        </div>
49
-        <div class="row form-row" id="row-new-theme">
50
-          <label for="input-new-theme">New theme</label>
51
-          <input id="input-new-theme" name="new-theme" type="file" />
52
-        </div>
53
-        <div class="row form-row" id="row-caption">
54
-          <label for="input-caption">
55
-            Name
56
-          </label>
57
-          <input id="input-new-caption" name="new-caption" type="text" autocomplete="off" placeholder="Add a new theme name" />
58
-        </div>
59
-        <div class="row form-row">
60
-          <button class="btn btn-outline-primary" name="btn-new-theme" id="btn-new-theme">Add</button>
61
-        </div>
62
-        <div class="row form-row">
63
-          <hr>
64
-        </div-->
65
-
66 50
         <div id="preview">
67 51
           <div style="background-color: black;">
68 52
             <div id="canvas">

+ 89 - 5
editor/theme.html ファイルの表示

@@ -1,8 +1,10 @@
1
-<html>
1
+<!DOCTYPE html>
2 2
 <html lang="en">
3 3
   <head>
4
-    <title>Audiogram::Theme Editor</title>
4
+    <title>Audiogram / Theme Editor</title>
5 5
     <meta charset="utf-8" />
6
+    <meta name="robots" content="noindex, nofollow" />
7
+    <meta name="googlebot" content="noindex, nofollow" />
6 8
     <meta name="viewport" content="width=device-width, initial-scale=1">
7 9
     <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css">
8 10
     <link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,300,600" rel="stylesheet" type="text/css">
@@ -10,7 +12,89 @@
10 12
     <link href="/css/editor.css" rel="stylesheet" type="text/css">
11 13
     <link href="/fonts/fonts.css" rel="stylesheet" type="text/css">
12 14
   </head>
13
-  <body>
14
-  	
15
+  <body class="loading">
16
+
17
+    <div class="container">
18
+
19
+      <h1>Theme Editor</h1>
20
+      <div>
21
+        <a href="/">Go back</a>
22
+      </div>
23
+      <div id="loading">
24
+        <div id="loading-bars">
25
+          <div class="r1"></div><div class="r2"></div><div class="r3"></div><div class="r4"></div><div class="r5"></div>
26
+        </div>
27
+        <div id="loading-message">Loading...</div>
28
+      </div>
29
+
30
+      <div id="loaded">
31
+
32
+        <div class="row form-row" id="row-theme">
33
+          <label for="input-theme">Theme</label>
34
+          <select id="input-theme" name="theme"></select>
35
+        </div>
36
+
37
+        <div class="row form-row" style="padding-bottom: 40px">
38
+          <button type="button" id="btn-delete-theme" class="left" data-confirm="Are you sure you want to delete this theme?"><i class="fa fa-trash"></i>Delete</button>
39
+        </div>
40
+
41
+        <div id="preview">
42
+          <div style="background-color: black;">
43
+            <div id="canvas">
44
+              <canvas width="640" height="360"></canvas>
45
+              <div id="preview-label">Preview</div>
46
+            </div>
47
+          </div>
48
+          <div id="minimap" class="hidden">
49
+            <svg width="640" height="80" xmlns="http://www.w3.org/2000/svg">
50
+              <defs>
51
+                <clipPath id="clip">
52
+                  <rect height="80" width="640" x="0" y="0"></rect>
53
+                </clipPath>
54
+              </defs>
55
+              <g class="waveform background">
56
+                <line x1="0" x2="640" y1="40" y2="40"></line>
57
+                <path></path>
58
+              </g>
59
+              <g class="waveform foreground" clip-path="url(#clip)">
60
+                <line x1="0" x2="640" y1="40" y2="40"></line>
61
+                <path></path>
62
+              </g>
63
+              <g class="brush"></g>
64
+              <g class="time">
65
+                <line x1="0" x2="0" y1="0" y2="80"></line>
66
+              </g>
67
+            </svg>
68
+          </div>
69
+        </div>
70
+
71
+        <div class="row form-row" style="padding: 20px 0 10px 0">
72
+          <p>
73
+            Please note: Theme assets can be only of png or jpeg file format
74
+          </p>
75
+        </div>
76
+
77
+        <div class="row form-row" id="row-new-theme" style="padding: 0 0 20px 0">
78
+          <label for="input-new-theme">New theme</label>
79
+          <input id="input-new-theme" name="new-theme" type="file" />
80
+        </div>
81
+
82
+        <div class="row form-row" id="row-caption">
83
+          <label for="input-caption">
84
+            Theme Name
85
+          </label>
86
+          <input id="input-new-caption" name="new-caption" type="text" autocomplete="off" placeholder="Add a new theme name" />
87
+        </div>
88
+        <div class="row form-row">
89
+          <button type="button" id="btn-new-theme" class="left"><i class="fa fa-arrow-circle-up"></i>Add</button>
90
+        </div>
91
+        
92
+
93
+      </div>
94
+
95
+    </div>
96
+
97
+    <script src="/js/bundle.js"></script>
98
+    <script src="/fonts/fonts.js"></script>
15 99
   </body>
16
-</html>
100
+</html>

+ 49 - 5
server/index.js ファイルの表示

@@ -16,10 +16,14 @@ var logger = require("../lib/logger/"),
16 16
 // Settings
17 17
 var serverSettings = require("../lib/settings/");
18 18
 
19
-var fs = require("fs");
19
+var fs = require("fs"),
20
+    bodyParser = require("body-parser");
20 21
 
21 22
 var app = express();
22 23
 
24
+var jsonParser = bodyParser.json();
25
+// var urlencodedParser = bodyParser.urlencoded({ extended: false });
26
+
23 27
 app.use(compression());
24 28
 app.use(logger.morgan());
25 29
 
@@ -72,13 +76,12 @@ const mt = multer(fileOptions).fields([{name: 'audio'}, {name: 'subtitle'}]);
72 76
 app.post("/submit/", [mt, render.validate, render.route]);
73 77
 
74 78
 // Upload new theme
75
-
76 79
 app.post("/theme/upload/", [multer(newThemeFileOptions).single("newTheme"), function (req, res) {
77 80
   var themesFile = path.join(serverSettings.settingsPath, "themes.json");
78 81
   fs.readFile(themesFile, "utf8", function readFileCallback(err, data) {
79 82
     if (err) {
80 83
       console.log('err', err);
81
-      return null;
84
+      res.send(JSON.stringify({status: 500, error: err}));
82 85
     } else {
83 86
       var caption = req.body.newCaption;
84 87
       var themes = JSON.parse(data);
@@ -89,14 +92,55 @@ app.post("/theme/upload/", [multer(newThemeFileOptions).single("newTheme"), func
89 92
       fs.writeFile(themesFile, jt, "utf8", function (err) {
90 93
         if (err) {
91 94
           console.log(err);
92
-          return null;
95
+          res.send(JSON.stringify({status: 500, error: err}));
93 96
         }
97
+        res.send(JSON.stringify({status: 200, success: "success"}));
94 98
       });
95 99
     }
96 100
   });
97
-  res.end();
98 101
 }]);
99 102
 
103
+// Delete theme
104
+app.post("/theme/delete/", jsonParser, function (req, res) {
105
+  var themesFile = path.join(serverSettings.settingsPath, "themes.json");
106
+  fs.readFile(themesFile, "utf8", function readFileCallback(err, data) {
107
+    if (err) {
108
+      console.log('err', err);
109
+      res.send(JSON.stringify({status: 500, error: err}));
110
+    } else {
111
+      var theme = req.body.theme;
112
+      var themes = JSON.parse(data);
113
+      
114
+      if (themes[theme]) {
115
+        var background = themes[theme]["backgroundImage"];
116
+        if (background) {
117
+          var asset = path.join(serverSettings.themeStoragePath, background);
118
+          try {
119
+            fs.unlink(asset, function(err) {
120
+              if (err) {
121
+                console.log('err', error);
122
+                res.send(JSON.stringify({status: 500, error: err}));
123
+              }
124
+              delete themes[theme];
125
+              var jt = JSON.stringify(themes);
126
+              fs.writeFile(themesFile, jt, "utf8", function (err) {
127
+                if (err) {
128
+                  console.log(err);
129
+                  res.send(JSON.stringify({status: 500, error: err}));
130
+                }
131
+                res.send(JSON.stringify({status: 200, success: "success"}));
132
+              });
133
+            });
134
+          } catch (err) {
135
+            console.log(err);
136
+            res.send(JSON.stringify({status: 500, error: err}));
137
+          }  
138
+        }
139
+      }
140
+    }
141
+  });
142
+});
143
+
100 144
 // Theme editor
101 145
 app.use("/theme/", express.static(path.join(__dirname, "..", "editor/theme.html")));
102 146
 

BIN
settings/backgrounds/nyp.png ファイルの表示


File diff suppressed because it is too large
+ 1 - 115
settings/themes.json