Twizy: Tuning Profile Editor

../../../../_images/profile-editor.png

This plugin has been added to the Twizy code. It’s used here as a more complex example of what can be done by plugins.

It’s a full featured SEVCON tuning profile editor including dialogs to load & save profiles from the OVMS configuration and to create & read base64 profile codes.

See Twizy documentation for details on tuning profiles and capabilities.

Install: not necessary if vehicle Twizy is configured (see “Twizy” menu). If you’d like to test this for another vehicle, add as a page plugin e.g. /test/profed.

profile-editor.htm (hint: right click, save as)

  1<!--
  2  Twizy page plugin: Tuning Profile Editor
  3  Note: included in firmware v3.2
  4-->
  5
  6<style>
  7.form-inline .form-control.slider-value {
  8  width: 70px;
  9}
 10.radio-list {
 11  height: 313px;
 12  overflow-y: auto;
 13  overflow-x: hidden;
 14  padding-right: 15px;
 15}
 16.radio-list .radio {
 17  overflow: hidden;
 18}
 19.radio-list .key {
 20  min-width: 20px;
 21  display: inline-block;
 22  text-align: center;
 23  margin: 0 10px;
 24}
 25.radio-list kbd {
 26  min-width: 60px;
 27  display: inline-block;
 28  text-align: center;
 29  margin: 0 20px 0 10px;
 30}
 31.radio-list .radio label {
 32  width: 100%;
 33  text-align: left;
 34  padding: 8px 30px;
 35}
 36.radio-list .radio label.active {
 37  background-color: #337ab7;
 38  color: #fff;
 39  outline: none;
 40}
 41.radio-list .radio label.active input {
 42  outline: none;
 43}
 44.night .radio-list .radio label:hover {
 45  color: #fff;
 46}
 47</style>
 48
 49<div class="panel panel-primary">
 50  <div class="panel-heading">Tuning Profile <span id="headkey">Editor</span></div>
 51  <div class="panel-body">
 52    <form action="#">
 53
 54    <div class="form-group">
 55      <div class="flex-group">
 56        <button type="button" class="btn btn-default action-new">New</button>
 57        <button type="button" class="btn btn-default action-load">Load…</button>
 58        <input type="hidden" id="input-key" name="key" value="">
 59        <input type="hidden" id="input-base64-reset" name="base64-reset" value="">
 60        <input type="text" class="form-control font-monospace" placeholder="Base64 profile code"
 61          name="base64" id="input-base64" value="" autocapitalize="none" autocorrect="off"
 62          autocomplete="off" spellcheck="false">
 63      </div>
 64    </div>
 65
 66    <ul class="nav nav-tabs">
 67      <li class="active"><a data-toggle="tab" href="#tab-drive" aria-expanded="true">Drive</a></li>
 68      <li class=""><a data-toggle="tab" href="#tab-recup" aria-expanded="false">Recup</a></li>
 69      <li class=""><a data-toggle="tab" href="#tab-ramps" aria-expanded="false">Ramps</a></li>
 70    </ul>
 71    <div class="tab-content">
 72
 73      <div id="tab-drive" class="tab-pane section-drive active in">
 74
 75        <fieldset id="part-speed">
 76          <legend>Speed</legend>
 77          <div class="form-horizontal">
 78            <div class="form-group">
 79              <label class="control-label col-sm-2" for="input-speed">Max speed:</label>
 80              <div class="col-sm-10"><div class="form-control slider" id="speed" /></div>
 81            </div>
 82            <div class="form-group">
 83              <label class="control-label col-sm-2" for="input-warn">Warn speed:</label>
 84              <div class="col-sm-10"><div class="form-control slider" id="warn" /></div>
 85            </div>
 86          </div>
 87        </fieldset>
 88
 89        <fieldset id="part-power">
 90          <legend>Power</legend>
 91          <div class="form-horizontal">
 92            <div class="form-group">
 93              <label class="control-label col-sm-2" for="input-current">Current:</label>
 94              <div class="col-sm-10"><div class="form-control slider" id="current" /></div>
 95            </div>
 96            <div class="form-group">
 97              <label class="control-label col-sm-2" for="input-torque">Torque:</label>
 98              <div class="col-sm-10"><div class="form-control slider" id="torque" /></div>
 99            </div>
100            <div class="form-group">
101              <label class="control-label col-sm-2" for="input-power_low">Power low:</label>
102              <div class="col-sm-10"><div class="form-control slider" id="power_low" /></div>
103            </div>
104            <div class="form-group">
105              <label class="control-label col-sm-2" for="input-power_high">Power high:</label>
106              <div class="col-sm-10"><div class="form-control slider" id="power_high" /></div>
107            </div>
108          </div>
109        </fieldset>
110
111        <fieldset id="part-drive">
112          <legend>Drive</legend>
113          <div class="form-horizontal">
114            <div class="form-group">
115              <label class="control-label col-sm-2" for="input-drive">Drive level:</label>
116              <div class="col-sm-10"><div class="form-control slider" id="drive" /></div>
117            </div>
118            <div class="form-group">
119              <label class="control-label col-sm-2" for="input-autodrive_ref">Auto 100% ref:</label>
120              <div class="col-sm-10"><div class="form-control slider" id="autodrive_ref" /></div>
121            </div>
122            <div class="form-group">
123              <label class="control-label col-sm-2" for="input-autodrive_minprc">Auto min level:</label>
124              <div class="col-sm-10"><div class="form-control slider" id="autodrive_minprc" /></div>
125            </div>
126          </div>
127        </fieldset>
128
129        <fieldset id="part-tsmapd">
130          <legend>Torque/Speed-Map: Drive</legend>
131          <div class="form-horizontal">
132            <div class="form-group">
133              <label class="control-label col-sm-2" for="input-tsd_prc1">Trq level 1:</label>
134              <div class="col-sm-10"><div class="form-control slider" id="tsd_prc1" /></div>
135            </div>
136            <div class="form-group">
137              <label class="control-label col-sm-2" for="input-tsd_spd1">…at speed:</label>
138              <div class="col-sm-10"><div class="form-control slider" id="tsd_spd1" /></div>
139            </div>
140            <div class="form-group">
141              <label class="control-label col-sm-2" for="input-tsd_prc2">Trq level 2:</label>
142              <div class="col-sm-10"><div class="form-control slider" id="tsd_prc2" /></div>
143            </div>
144            <div class="form-group">
145              <label class="control-label col-sm-2" for="input-tsd_spd2">…at speed:</label>
146              <div class="col-sm-10"><div class="form-control slider" id="tsd_spd2" /></div>
147            </div>
148            <div class="form-group">
149              <label class="control-label col-sm-2" for="input-tsd_prc3">Trq level 3:</label>
150              <div class="col-sm-10"><div class="form-control slider" id="tsd_prc3" /></div>
151            </div>
152            <div class="form-group">
153              <label class="control-label col-sm-2" for="input-tsd_spd3">…at speed:</label>
154              <div class="col-sm-10"><div class="form-control slider" id="tsd_spd3" /></div>
155            </div>
156            <div class="form-group">
157              <label class="control-label col-sm-2" for="input-tsd_prc4">Trq level 4:</label>
158              <div class="col-sm-10"><div class="form-control slider" id="tsd_prc4" /></div>
159            </div>
160            <div class="form-group">
161              <label class="control-label col-sm-2" for="input-tsd_spd4">…at speed:</label>
162              <div class="col-sm-10"><div class="form-control slider" id="tsd_spd4" /></div>
163            </div>
164          </div>
165        </fieldset>
166
167      </div>
168
169      <div id="tab-recup" class="tab-pane section-recup">
170
171        <fieldset id="part-recup">
172          <legend>Recup</legend>
173          <div class="form-horizontal">
174            <div class="form-group">
175              <label class="control-label col-sm-2" for="input-neutral">Neutral level:</label>
176              <div class="col-sm-10"><div class="form-control slider" id="neutral" /></div>
177            </div>
178            <div class="form-group">
179              <label class="control-label col-sm-2" for="input-brake">Brake level:</label>
180              <div class="col-sm-10"><div class="form-control slider" id="brake" /></div>
181            </div>
182            <div class="form-group">
183              <label class="control-label col-sm-2" for="input-autorecup_ref">Auto 100% ref:</label>
184              <div class="col-sm-10"><div class="form-control slider" id="autorecup_ref" /></div>
185            </div>
186            <div class="form-group">
187              <label class="control-label col-sm-2" for="input-autorecup_minprc">Auto min level:</label>
188              <div class="col-sm-10"><div class="form-control slider" id="autorecup_minprc" /></div>
189            </div>
190          </div>
191        </fieldset>
192
193        <fieldset id="part-tsmapn">
194          <legend>Torque/Speed-Map: Neutral</legend>
195          <div class="form-horizontal">
196            <div class="form-group">
197              <label class="control-label col-sm-2" for="input-tsn_prc1">Trq level 1:</label>
198              <div class="col-sm-10"><div class="form-control slider" id="tsn_prc1" /></div>
199            </div>
200            <div class="form-group">
201              <label class="control-label col-sm-2" for="input-tsn_spd1">…at speed:</label>
202              <div class="col-sm-10"><div class="form-control slider" id="tsn_spd1" /></div>
203            </div>
204            <div class="form-group">
205              <label class="control-label col-sm-2" for="input-tsn_prc2">Trq level 2:</label>
206              <div class="col-sm-10"><div class="form-control slider" id="tsn_prc2" /></div>
207            </div>
208            <div class="form-group">
209              <label class="control-label col-sm-2" for="input-tsn_spd2">…at speed:</label>
210              <div class="col-sm-10"><div class="form-control slider" id="tsn_spd2" /></div>
211            </div>
212            <div class="form-group">
213              <label class="control-label col-sm-2" for="input-tsn_prc3">Trq level 3:</label>
214              <div class="col-sm-10"><div class="form-control slider" id="tsn_prc3" /></div>
215            </div>
216            <div class="form-group">
217              <label class="control-label col-sm-2" for="input-tsn_spd3">…at speed:</label>
218              <div class="col-sm-10"><div class="form-control slider" id="tsn_spd3" /></div>
219            </div>
220            <div class="form-group">
221              <label class="control-label col-sm-2" for="input-tsn_prc4">Trq level 4:</label>
222              <div class="col-sm-10"><div class="form-control slider" id="tsn_prc4" /></div>
223            </div>
224            <div class="form-group">
225              <label class="control-label col-sm-2" for="input-tsn_spd4">…at speed:</label>
226              <div class="col-sm-10"><div class="form-control slider" id="tsn_spd4" /></div>
227            </div>
228          </div>
229        </fieldset>
230
231        <fieldset id="part-tsmapb">
232          <legend>Torque/Speed-Map: Brake</legend>
233          <div class="form-horizontal">
234            <div class="form-group">
235              <label class="control-label col-sm-2" for="input-tsb_prc1">Trq level 1:</label>
236              <div class="col-sm-10"><div class="form-control slider" id="tsb_prc1" /></div>
237            </div>
238            <div class="form-group">
239              <label class="control-label col-sm-2" for="input-tsb_spd1">…at speed:</label>
240              <div class="col-sm-10"><div class="form-control slider" id="tsb_spd1" /></div>
241            </div>
242            <div class="form-group">
243              <label class="control-label col-sm-2" for="input-tsb_prc2">Trq level 2:</label>
244              <div class="col-sm-10"><div class="form-control slider" id="tsb_prc2" /></div>
245            </div>
246            <div class="form-group">
247              <label class="control-label col-sm-2" for="input-tsb_spd2">…at speed:</label>
248              <div class="col-sm-10"><div class="form-control slider" id="tsb_spd2" /></div>
249            </div>
250            <div class="form-group">
251              <label class="control-label col-sm-2" for="input-tsb_prc3">Trq level 3:</label>
252              <div class="col-sm-10"><div class="form-control slider" id="tsb_prc3" /></div>
253            </div>
254            <div class="form-group">
255              <label class="control-label col-sm-2" for="input-tsb_spd3">…at speed:</label>
256              <div class="col-sm-10"><div class="form-control slider" id="tsb_spd3" /></div>
257            </div>
258            <div class="form-group">
259              <label class="control-label col-sm-2" for="input-tsb_prc4">Trq level 4:</label>
260              <div class="col-sm-10"><div class="form-control slider" id="tsb_prc4" /></div>
261            </div>
262            <div class="form-group">
263              <label class="control-label col-sm-2" for="input-tsb_spd4">…at speed:</label>
264              <div class="col-sm-10"><div class="form-control slider" id="tsb_spd4" /></div>
265            </div>
266          </div>
267        </fieldset>
268
269      </div>
270
271      <div id="tab-ramps" class="tab-pane section-ramps">
272
273        <fieldset id="part-rampsglobal">
274          <legend>General</legend>
275          <div class="form-horizontal">
276            <div class="form-group">
277              <label class="control-label col-sm-2" for="input-ramplimit_accel">Limit up:</label>
278              <div class="col-sm-10"><div class="form-control slider" id="ramplimit_accel" /></div>
279            </div>
280            <div class="form-group">
281              <label class="control-label col-sm-2" for="input-ramplimit_decel">Limit down:</label>
282              <div class="col-sm-10"><div class="form-control slider" id="ramplimit_decel" /></div>
283            </div>
284            <div class="form-group">
285              <label class="control-label col-sm-2" for="input-smooth">Smoothing:</label>
286              <div class="col-sm-10"><div class="form-control slider" id="smooth" /></div>
287            </div>
288          </div>
289        </fieldset>
290
291        <fieldset id="part-ramps">
292          <legend>Ramp Speeds</legend>
293          <div class="form-horizontal">
294            <div class="form-group">
295              <label class="control-label col-sm-2" for="input-ramp_start">Start/reverse:</label>
296              <div class="col-sm-10"><div class="form-control slider" id="ramp_start" /></div>
297            </div>
298            <div class="form-group">
299              <label class="control-label col-sm-2" for="input-ramp_accel">Accelerate:</label>
300              <div class="col-sm-10"><div class="form-control slider" id="ramp_accel" /></div>
301            </div>
302            <div class="form-group">
303              <label class="control-label col-sm-2" for="input-ramp_decel">Decelerate:</label>
304              <div class="col-sm-10"><div class="form-control slider" id="ramp_decel" /></div>
305            </div>
306            <div class="form-group">
307              <label class="control-label col-sm-2" for="input-ramp_neutral">Neutral:</label>
308              <div class="col-sm-10"><div class="form-control slider" id="ramp_neutral" /></div>
309            </div>
310            <div class="form-group">
311              <label class="control-label col-sm-2" for="input-ramp_brake">Brake:</label>
312              <div class="col-sm-10"><div class="form-control slider" id="ramp_brake" /></div>
313            </div>
314          </div>
315        </fieldset>
316
317      </div>
318
319    </div>
320
321    <br>
322    <div class="form-horizontal">
323      <div class="form-group">
324        <label class="control-label col-sm-2" for="input-label">Label:</label>
325        <div class="col-sm-10">
326          <input type="text" class="form-control" placeholder="Short button label"
327            name="label" id="input-label" value="" autocapitalize="none" autocorrect="off"
328            autocomplete="off" spellcheck="false">
329        </div>
330      </div>
331      <div class="form-group">
332        <label class="control-label col-sm-2" for="input-title">Title:</label>
333        <div class="col-sm-10">
334          <input type="text" class="form-control" placeholder="optional title/name"
335            name="title" id="input-title" value="" autocapitalize="none" autocorrect="off"
336            autocomplete="off" spellcheck="false">
337        </div>
338      </div>
339    </div>
340
341    <br>
342    <div class="text-center">
343      <button type="button" class="btn btn-default action-reset">Reset</button>
344      <button type="button" class="btn btn-default action-saveas">Save as…</button>
345      <button type="button" class="btn btn-primary action-save">Save</button>
346    </div>
347
348    </form>
349  </div>
350</div>
351
352<div id="key-dialog" />
353
354<script>
355(function(){
356
357  // init sliders:
358
359  $('#speed').slider({ unit:'kph', min:6, max:120, default:80, value:80, checked:false });
360  $('#warn').slider({ unit:'kph', min:6, max:120, default:89, value:89, checked:false });
361
362  $('#current').slider({ unit:'%', min:10, max:123, default:100, value:100, checked:false });
363  $('#torque').slider({ unit:'%', min:10, max:130, default:100, value:100, checked:false });
364  $('#power_low').slider({ unit:'%', min:10, max:139, default:100, value:100, checked:false });
365  $('#power_high').slider({ unit:'%', min:10, max:130, default:100, value:100, checked:false });
366
367  $('#drive').slider({ unit:'%', min:10, max:100, default:100, value:100, checked:false });
368  $('#autodrive_ref').slider({ unit:'kW', min:0, max:25, step:0.1, default:0, value:0, checked:false });
369  $('#autodrive_minprc').slider({ unit:'%', min:0, max:100, default:0, value:0, checked:false });
370
371  $('#tsd_prc1').slider({ unit:'%', min:0, max:100, default:100, value:100, checked:false });
372  $('#tsd_spd1').slider({ unit:'kph', min:0, max:120, default:33, value:33, checked:false });
373  $('#tsd_prc2').slider({ unit:'%', min:0, max:100, default:100, value:100, checked:false });
374  $('#tsd_spd2').slider({ unit:'kph', min:0, max:120, default:39, value:39, checked:false });
375  $('#tsd_prc3').slider({ unit:'%', min:0, max:100, default:100, value:100, checked:false });
376  $('#tsd_spd3').slider({ unit:'kph', min:0, max:120, default:50, value:50, checked:false });
377  $('#tsd_prc4').slider({ unit:'%', min:0, max:100, default:100, value:100, checked:false });
378  $('#tsd_spd4').slider({ unit:'kph', min:0, max:120, default:66, value:66, checked:false });
379
380  $('#neutral').slider({ unit:'%', min:0, max:100, default:18, value:18, checked:false });
381  $('#brake').slider({ unit:'%', min:0, max:100, default:18, value:18, checked:false });
382  $('#autorecup_ref').slider({ unit:'kW', min:0, max:25, step:0.1, default:0, value:0, checked:false });
383  $('#autorecup_minprc').slider({ unit:'%', min:0, max:100, default:0, value:0, checked:false });
384
385  $('#tsn_prc1').slider({ unit:'%', min:0, max:100, default:100, value:100, checked:false });
386  $('#tsn_spd1').slider({ unit:'kph', min:0, max:120, default:33, value:33, checked:false });
387  $('#tsn_prc2').slider({ unit:'%', min:0, max:100, default:80, value:80, checked:false });
388  $('#tsn_spd2').slider({ unit:'kph', min:0, max:120, default:39, value:39, checked:false });
389  $('#tsn_prc3').slider({ unit:'%', min:0, max:100, default:50, value:50, checked:false });
390  $('#tsn_spd3').slider({ unit:'kph', min:0, max:120, default:50, value:50, checked:false });
391  $('#tsn_prc4').slider({ unit:'%', min:0, max:100, default:20, value:20, checked:false });
392  $('#tsn_spd4').slider({ unit:'kph', min:0, max:120, default:66, value:66, checked:false });
393
394  $('#tsb_prc1').slider({ unit:'%', min:0, max:100, default:100, value:100, checked:false });
395  $('#tsb_spd1').slider({ unit:'kph', min:0, max:120, default:33, value:33, checked:false });
396  $('#tsb_prc2').slider({ unit:'%', min:0, max:100, default:80, value:80, checked:false });
397  $('#tsb_spd2').slider({ unit:'kph', min:0, max:120, default:39, value:39, checked:false });
398  $('#tsb_prc3').slider({ unit:'%', min:0, max:100, default:50, value:50, checked:false });
399  $('#tsb_spd3').slider({ unit:'kph', min:0, max:120, default:50, value:50, checked:false });
400  $('#tsb_prc4').slider({ unit:'%', min:0, max:100, default:20, value:20, checked:false });
401  $('#tsb_spd4').slider({ unit:'kph', min:0, max:120, default:66, value:66, checked:false });
402
403
404  $('#ramplimit_accel').slider({ unit:'%', min:1, max:100, default:30, value:30, checked:false });
405  $('#ramplimit_decel').slider({ unit:'%', min:0, max:100, default:30, value:30, checked:false });
406  $('#smooth').slider({ unit:'%', min:0, max:100, default:70, value:70, checked:false });
407
408  $('#ramp_start').slider({ unit:'‰', min:1, max:250, default:40, value:40, checked:false });
409  $('#ramp_accel').slider({ unit:'%', min:1, max:100, default:25, value:25, checked:false });
410  $('#ramp_decel').slider({ unit:'%', min:0, max:100, default:20, value:20, checked:false });
411  $('#ramp_neutral').slider({ unit:'%', min:0, max:100, default:40, value:40, checked:false });
412  $('#ramp_brake').slider({ unit:'%', min:0, max:100, default:40, value:40, checked:false });
413
414  // profile handling:
415
416  const keys = [
417    "checksum",
418    "speed",
419    "warn",
420    "torque",
421    "power_low",
422    "power_high",
423    "drive",
424    "neutral",
425    "brake",
426    "tsd_spd1",
427    "tsd_spd2",
428    "tsd_spd3",
429    "tsd_spd4",
430    "tsd_prc1",
431    "tsd_prc2",
432    "tsd_prc3",
433    "tsd_prc4",
434    "tsn_spd1",
435    "tsn_spd2",
436    "tsn_spd3",
437    "tsn_spd4",
438    "tsn_prc1",
439    "tsn_prc2",
440    "tsn_prc3",
441    "tsn_prc4",
442    "tsb_spd1",
443    "tsb_spd2",
444    "tsb_spd3",
445    "tsb_spd4",
446    "tsb_prc1",
447    "tsb_prc2",
448    "tsb_prc3",
449    "tsb_prc4",
450    "ramp_start",
451    "ramp_accel",
452    "ramp_decel",
453    "ramp_neutral",
454    "ramp_brake",
455    "smooth",
456    "brakelight_on",
457    "brakelight_off",
458    "ramplimit_accel",
459    "ramplimit_decel",
460    "autorecup_minprc",
461    "autorecup_ref",
462    "autodrive_minprc",
463    "autodrive_ref",
464    "current",
465  ];
466
467  const scale = {
468    "autodrive_ref": 0.1,
469    "autorecup_ref": 0.1,
470  };
471
472  var profile = {};
473
474  var plist = [
475    { label: "–", title: "Working Set" },
476    { label: "PWR", title: "Power" },
477    { label: "ECO", title: "Economy" },
478    { label: "ICE", title: "Winter" },
479  ];
480
481  // load profile list:
482  var plistloader = loadcmd('config list xrt').then(function(data) {
483    var lines = data.split('\n'), line, i, key;
484    for (i = 0; i < lines.length; i++) {
485      line = lines[i].match(/profile([0-9]{2})\.?([^:]*): (.*)/);
486      if (line && line.length == 4) {
487        key = Number(line[1]);
488        if (key < 1 || key > 99) continue;
489        if (!plist[key]) plist[key] = {};
490        plist[key][line[2]||"profile"] = line[3];
491      }
492    }
493  });
494
495  // current control → power ranges:
496  function currentControl(on, trigger) {
497		if (on) {
498			$("#input-torque, #input-power_low, #input-power_high").slider({ max: 254 });
499		} else {
500			$("#input-torque, #input-power_high").slider({ max: 130 });
501			$("#input-power_low").slider({ max: 139 });
502		}
503  }
504  $('#input-current').on('change', function(ev) {
505    currentControl(this.checked);
506    $("#input-torque, #input-power_low, #input-power_high").trigger('change');
507  });
508
509  // load profile into sliders:
510  function loadProfile() {
511    currentControl(profile["current"] >= 0);
512    $.map(profile, function(val, key) {
513      $('#input-'+key).slider({ value: (val >= 0) ? (val * (scale[key]||1)) : null, checked: (val >= 0) });
514    });
515  }
516
517  // calculate profile checksum:
518  function calcChecksum() {
519    var checksum, i;
520    checksum = 0x0101;
521    for (i=1; i<keys.length; i++)
522      checksum += profile[keys[i]] + 1;
523    if ((checksum & 0x0ff) == 0)
524      checksum >>= 8;
525    return (checksum & 0x0ff) - 1;
526  }
527
528  // load a base64 string into profile:
529  function loadBase64(base64) {
530    var bin = atob(base64);
531    $.map(keys, function(key, i) { profile[key] = (bin.charCodeAt(i)||0) - 1; });
532    if (profile["checksum"] == calcChecksum()) {
533      loadProfile();
534      return true;
535    } else {
536      confirmdialog("Profile Error", "Invalid base64 code (checksum mismatch)", ["Close"]);
537      return false;
538    }
539  }
540
541  // make a base64 string from profile:
542  function makeBase64() {
543    profile["checksum"] = calcChecksum();
544    var u8 = new Uint8Array(keys.length);
545    $.map(keys, function(key, i) { u8[i] = profile[key] + 1; });
546    return btoa(String.fromCharCode.apply(null, u8));
547  }
548
549  // load a profile:
550  function loadKey(key) {
551    if (key < 0 || key > 99) return;
552    plistloader.then(function() {
553      $('#input-label').val(plist[key] && plist[key]["label"] || "");
554      $('#input-title').val(plist[key] && plist[key]["title"] || "");
555      loadcmd('xrt cfg get ' + key).fail(function(request, textStatus, errorThrown) {
556        confirmdialog("Load Profile", xhrErrorInfo(request, textStatus, errorThrown), ["Close"]);
557      }).done(function(res) {
558        var base64;
559        if (res.match(/error/i))
560          base64 = "AQ";
561        else
562          base64 = res.substr(res.lastIndexOf(' ')+1);
563        if (loadBase64(base64)) {
564          $('#input-key').val(key);
565          $('#input-base64, #input-base64-reset').val(base64);
566          $('#headkey').text('#' + key);
567        }
568      });
569    });
570  }
571
572  // save profile:
573  function saveKey(key) {
574    if (key < 0 || key > 99) return;
575    var base64 = $('#input-base64').val(),
576      label = $('#input-label').val().replace(/"/g, '\\"'),
577      title = $('#input-title').val().replace(/"/g, '\\"'),
578      ckey = 'profile' + ((key<10)?'0':'') + key;
579    var cmd = 'xrt cfg set ' + key + ' ' + base64 + ' "' + label + '" "' + title + '"';
580    loadcmd(cmd).fail(function(request, textStatus, errorThrown) {
581      confirmdialog("Save Profile", xhrErrorInfo(request, textStatus, errorThrown), ["Close"]);
582    }).done(function(res) {
583      confirmdialog("Save Profile", res, ["Close"]);
584      if (!res.match(/error/i)) {
585        if (key > 0) {
586          if (!plist[key]) plist[key] = {};
587          plist[key]["label"] = label;
588          plist[key]["title"] = title;
589        }
590        $('#input-key').val(key);
591        $('#input-base64-reset').val(base64);
592        $('#headkey').text('#' + key);
593      }
594    });
595  }
596
597  // base64 input:
598  $('#input-base64').on('change', function(ev) {
599    var base64 = $(this).val() || "AQ";
600    if (loadBase64(base64))
601      $('#input-base64-reset').val(base64);
602  });
603
604  // update profile & base64 on slider changes:
605  $('.slider-value').on('change', function(ev) {
606    var key = this.name;
607    var val = Math.max(this.min, Math.min(this.max, 1*this.value));
608    profile[key] = this.checked ? (val / (scale[key]||1)) : -1;
609    var base64 = makeBase64();
610    $('#input-base64').val(base64);
611  });
612
613  // prep key dialog:
614  $('#key-dialog').dialog({
615    show: false,
616    onShow: function(input) {
617      var $this = $(this);
618      $this.addClass("loading");
619      plistloader.then(function(data) {
620        var curkey = $('#input-key').val() || 0, i, label, title;
621        $plist = $('<div class="radio-list" data-toggle="buttons" />');
622        for (i = 0; i <= Math.min(99, plist.length + 2); i++) {
623          if (plist[i] && (i==0 || plist[i].profile)) {
624            label = plist[i].label || "–";
625            title = plist[i].title || (plist[i].profile.substr(0, 10) + "…");
626          } else {
627            label = "–";
628            title = "–new–";
629          }
630          $plist.append('<div class="radio"><label class="btn">' +
631            '<input type="radio" name="key" value="' + i + '"><span class="key">' +
632            ((i==0) ? "WS" : ("#" + ((i<10)?'0':'') + i)) + '</span> <kbd>' +
633            encode_html(label) + '</kbd> ' + encode_html(title) + '</label></div>');
634        }
635        $this.find('.modal-body').append($plist).find('input[value="'+curkey+'"]')
636          .prop("checked", true).parent().addClass("active");
637        $plist
638          .on('dblclick', 'label.btn', function(ev) { $this.dialog('triggerButton', 1); })
639          .on('keypress', function(ev) { if (ev.which==13) $this.dialog('triggerButton', 1); });
640        $this.removeClass("loading");
641      });
642    },
643    onShown: function(input) {
644      $(this).find('.btn.active').focus();
645    },
646    onHide: function(input) {
647      var $this = $(this), dlg = $this.data("dialog");
648      var key = $this.find('input[name="key"]:checked').val();
649      if (key !== undefined && input.button && input.button.index)
650        dlg.options.onAction.call(this, key);
651    },
652  });
653
654  // buttons:
655  $('.action-new').on('click', function(ev) {
656    $('.slider').slider({ value: null });
657    $('#headkey').text('Editor');
658    $('#input-base64, #input-label, #input-title').val('').trigger('change');
659  });
660  $('.action-reset').on('click', function(ev) {
661    $('#input-base64').val($('#input-base64-reset').val()).trigger('change');
662  });
663  $('.action-load').on('click', function(ev) {
664    $('#key-dialog').dialog({
665      show: true, title: 'Load Profile', body: '',
666      buttons: [{ label: 'Cancel', btnClass: 'default' },{ label: 'Load', btnClass: 'primary' }],
667      onAction: function(key) { loadKey(key) },
668    });
669  });
670  $('.action-saveas').on('click', function(ev) {
671    $('#key-dialog').dialog({
672      show: true, title: 'Save Profile', body: '',
673      buttons: [{ label: 'Cancel', btnClass: 'default' },{ label: 'Save', btnClass: 'primary' }],
674      onAction: function(key) { saveKey(key) },
675    });
676  });
677  $('.action-save').on('click', function(ev) {
678    var key = $('#input-key').val();
679    if (key === "")
680      $('.action-saveas').trigger('click');
681    else
682      saveKey(key);
683  });
684
685  // start: load profile / open load dialog:
686  if (page.params["key"] !== undefined)
687    loadKey(page.params["key"]);
688  else
689    $('.action-load').trigger('click');
690
691})();
692</script>