Fossil

Diff
Login

Differences From Artifact [3e9725ffae]:

To Artifact [d3ae252cb5]:


11
12
13
14
15
16
17
18

19
20
21
22
23
24
25

26
27
28





29
30
31







32
33
34
35

36
37
38
39
40
41
42
43
44
45
46

47
48
49










50



51
52
53
54
55
56
57
11
12
13
14
15
16
17

18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34



35
36
37
38
39
40
41




42
43
44
45
46
47
48
49
50
51
52

53
54
55
56
57
58
59
60
61
62
63
64
65
66

67
68
69
70
71
72
73
74
75
76







-
+







+



+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
-
-
-
-
+










-
+



+
+
+
+
+
+
+
+
+
+
-
+
+
+







  ***********************************************************************

  This is the main entry point for the WASM rendition of fossil's
  /pikchrshow app. It sets up the various UI bits, loads a Worker for
  the pikchr process, and manages the communication between the UI and
  worker.

  API dependencies: fossil.dom, fossil.storage
  API dependencies: fossil.dom, fossil.copybutton, fossil.storage
*/
(function(F/*fossil object*/){
  'use strict';

  /* Recall that the 'self' symbol, except where locally
     overwritten, refers to the global window or worker object. */

  const D = F.dom;
  /** Name of the stored copy of this app's config. */
  const configStorageKey = 'pikchrshow-config';

  /* querySelectorAll() proxy */
  const EAll = function(/*[element=document,] cssSelector*/){
    return (arguments.length>1 ? arguments[0] : document)
      .querySelectorAll(arguments[arguments.length-1]);
  };
  /**
     The PikchrFiddle object is intended to be the primary app-level
     object for the main-thread side of the fiddle application. It
  /* querySelector() proxy */
  const E = function(/*[element=document,] cssSelector*/){
    return (arguments.length>1 ? arguments[0] : document)
      .querySelector(arguments[arguments.length-1]);
  };

  /** The main application object. */
     uses a worker thread to load the WASM module and communicate
     with it.
  */
  const PS/*local convenience alias*/ = F.PikchrShow/*canonical name*/ = {
  const PS = {
    /* Config options. */
    config: {
      /* If true, display input/output areas side-by-side, else stack
         them vertically. */
      sideBySide: true,
      /* If true, swap positions of the input/output areas. */
      swapInOut: false,
      /* If true, the SVG is allowed to resize to fit the parent
         content area, else the parent is resized to fit the rendered
         SVG (as sized by pikchr). */
      renderAutoScale: false,
      renderAutofit: false,
      /* If true, automatically render while the user is typing. */
      renderWhileTyping: false
    },
    /* Various DOM elements. */
    e: {
      previewCopyButton: E('#preview-copy-button'),
      previewModeLabel: E('label[for=preview-copy-button]'),
      zoneOutputButtons: E('.zone-wrapper.output > legend > .button-bar'),
      outText: E('#pikchr-output-text'),
      pikOutWrapper: E('#pikchr-output-wrapper'),
      pikOut: E('#pikchr-output')
    },
    renderModes: ['svg'/*SVG must be at index 0*/,'markdown', 'wiki', 'text'],
    renderMode: 'html'/*one of: 'text','html'*/,
    renderModeLabels: {
      svg: 'SVG', markdown: 'Markdown', wiki: 'Fossil Wiki', text: 'Text'
    },
    _msgMap: {},
    /** Adds a worker message handler for messages of the given
        type. */
    addMsgHandler: function f(type,callback){
      if(Array.isArray(type)){
        type.forEach((t)=>this.addMsgHandler(t, callback));
        return this;
83
84
85
86
87
88
89
90

91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
102
103
104
105
106
107
108

109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127











128
129
130
131
132
133
134







-
+


















-
-
-
-
-
-
-
-
-
-
-







      return this;
    },
    /** Stores this object's config in the browser's storage. */
    storeConfig: function(){
      F.storage.setJSON(configStorageKey,this.config);
    }
  };

  PS.renderModes.selectedIndex = 0;
  PS._config = F.storage.getJSON(configStorageKey);
  if(PS._config){
    /* Copy all properties to PS.config which are currently in
       PS._config. We don't bother copying any other properties: those
       would be stale/removed config entries. */
    Object.keys(PS.config).forEach(function(k){
      if(PS._config.hasOwnProperty(k)){
        PS.config[k] = PS._config[k];
      }
    });
    delete PS._config;
  }

  PS.worker = new Worker('builtin/extsrc/pikchr-worker.js');
  PS.worker.onmessage = (ev)=>PS.runMsgHandlers(ev.data);
  PS.addMsgHandler('stdout', console.log.bind(console));
  PS.addMsgHandler('stderr', console.error.bind(console));

  /* querySelectorAll() proxy */
  const EAll = function(/*[element=document,] cssSelector*/){
    return (arguments.length>1 ? arguments[0] : document)
      .querySelectorAll(arguments[arguments.length-1]);
  };
  /* querySelector() proxy */
  const E = function(/*[element=document,] cssSelector*/){
    return (arguments.length>1 ? arguments[0] : document)
      .querySelector(arguments[arguments.length-1]);
  };

  /** Handles status updates from the Module object. */
  PS.addMsgHandler('module', function f(ev){
    ev = ev.data;
    if('status'!==ev.type){
      console.warn("Unexpected module-type message:",ev);
      return;
    }
154
155
156
157
158
159
160



161
162
163
164
165
166
167
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178







+
+
+







      /* The module can post messages about fatal problems,
         e.g. an exit() being triggered or assertion failure,
         after the last "load" message has arrived, so
         leave f.ui.status and message listener intact. */
    }
  });

  PS.e.previewModeLabel.innerText =
    PS.renderModeLabels[PS.renderModes[PS.renderModes.selectedIndex]];

  /**
     The 'pikchrshow-ready' event is fired (with no payload) when the
     wasm module has finished loading. */
  PS.addMsgHandler('pikchrshow-ready', function(){
    PS.clearMsgHandlers('pikchrshow-ready');
    F.page.onPikchrshowLoaded();
  });
285
286
287
288
289
290
291
292
293
294
295
296
297


298
299
300


301


302
303
304












305

306

307
308
309





310

311
312
313
314

315
316
317
318
319

320
321
322
323

324
325
326


327
328
329
330

331
332
333
334
335
336




337
338


339
340
341
342
343
344
345
296
297
298
299
300
301
302


303
304


305
306



307
308
309
310
311



312
313
314
315
316
317
318
319
320
321
322
323
324
325

326



327
328
329
330
331
332
333
334

335

336





337




338



339
340
341
342
343

344






345
346
347
348
349
350
351
352
353
354
355
356
357
358
359







-
-


-
-
+
+
-
-
-
+
+

+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+

+
-
+
-
-
-
+
+
+
+
+

+

-

-
+
-
-
-
-
-
+
-
-
-
-
+
-
-
-
+
+



-
+
-
-
-
-
-
-
+
+
+
+


+
+







      preStartWork();
      this.wMsg('pikchr',{
        pikchr: txt,
        darkMode: !!window.fossil.config.skin.isDark
      });
    };

    const eOut = E('#pikchr-output');
    const eOutWrapper = E('#pikchr-output-wrapper');
    PS.addMsgHandler('pikchr', function(ev){
      const m = ev.data;
      eOut.classList[m.isError ? 'add' : 'remove']('error');
      eOut.dataset.pikchr = m.pikchr;
      PS.e.pikOut.classList[m.isError ? 'add' : 'remove']('error');
      PS.e.pikOut.dataset.pikchr = m.pikchr;
      let content;
      let sz;
      switch(PS.renderMode){
      const mode = PS.renderModes[PS.renderModes.selectedIndex];
      switch(mode){
          case 'text':
          case 'markdown':
          case 'wiki': {
            content = '<textarea>'+m.result+'</textarea>';
            eOut.classList.add('text');
            eOutWrapper.classList.add('text');
            const body = [m.result];
            if('markdown'===mode){
              body.unshift('```pikchr');
              body.push('```');
            }else if('wiki'===mode){
              body.unshift('<verbatim type="pikchr">');
              body.push('</verbatim>');
            }
            PS.e.outText.value = body.join('\n');
            PS.e.outText.classList.remove('hidden');
            PS.e.pikOut.classList.add('hidden');
            PS.e.pikOutWrapper.classList.add('text');
            break;
          }
          default:
          case 'svg':
            content = m.result;
            eOut.classList.remove('text');
            eOutWrapper.classList.remove('text');
            PS.e.outText.classList.add('hidden');
            PS.e.pikOut.classList.remove('hidden');
            PS.e.pikOutWrapper.classList.remove('text');
            PS.e.pikOut.innerHTML = m.result;
            PS.e.outText.value = m.result/*for clipboard copy*/;
            break;
          default: throw new Error("Unhandled render mode: "+mode);
      }
      eOut.innerHTML = content;
      let vw = null, vh = null;
      if(!PS.config.renderAutoScale
      if('svg'===mode && !PS.config.renderAutofit && !m.isError){
         && !m.isError && 'html'===PS.renderMode){
        const svg = E(eOut,':scope > svg');
        const vb = svg ? svg.getAttribute('viewBox').split(' ') : false;
        if(vb && 4===vb.length){
          vw = (+vb[2] + 10)+'px';
        vw = m.width; vh = m.height;
          vh = (+vb[3] + 10)+'px';
        }else if(svg){
          console.warn("SVG element is missing viewBox attribute.");
        }
      }
      }
      eOut.style.width = vw;
      eOut.style.height = vh;
      PS.e.pikOut.style.width = vw ? vw+'px' : null;
      PS.e.pikOut.style.height = vh ? vh+'px' : null;
    })/*'pikchr' msg handler*/;

    E('#btn-render-mode').addEventListener('click',function(){
      let mode = PS.renderMode;
      const modes = PS.renderModes;
      const modes = ['text','html'];
      let ndx = modes.indexOf(mode) + 1;
      if(ndx>=modes.length) ndx = 0;
      PS.renderMode = modes[ndx];
      if(eOut.dataset.pikchr){
        PS.render(eOut.dataset.pikchr);
      modes.selectedIndex = (modes.selectedIndex + 1) % modes.length;
      PS.e.previewModeLabel.innerText = PS.renderModeLabels[modes[modes.selectedIndex]];
      if(PS.e.pikOut.dataset.pikchr){
        PS.render(PS.e.pikOut.dataset.pikchr);
      }
    });
    F.copyButton(PS.e.previewCopyButton, {copyFromElement: PS.e.outText});
    PS.e.previewModeLabel.addEventListener('click', ()=>PS.e.previewCopyButton.click(), false);

    PS.addMsgHandler('working',function f(ev){
      switch(ev.data){
          case 'start': /* See notes in preStartWork(). */; return;
          case 'end':
            //preStartWork._.pageTitle.innerText = preStartWork._.pageTitleOrig;
            btnRender.innerText = preStartWork._.btnLabel;
376
377
378
379
380
381
382
383
384


385
386
387


388
389
390
391
392
393
394
390
391
392
393
394
395
396


397
398
399


400
401
402
403
404
405
406
407
408







-
-
+
+

-
-
+
+







          e.dispatchEvent(new Event('change'));
        }
        e.addEventListener('change', function(){
          PS.config[this.dataset.config] = this.checked;
          PS.storeConfig();
        }, false);
      });
    E('#opt-cb-autoscale').addEventListener('change',function(){
      /* PS.config.renderAutoScale was set by the data-config
    E('#opt-cb-autofit').addEventListener('change',function(){
      /* PS.config.renderAutofit was set by the data-config
         event handler. */
      if('html'==PS.renderMode && eOut.dataset.pikchr){
        PS.render(eOut.dataset.pikchr);
      if(0==PS.renderModes.selectedIndex && PS.e.pikOut.dataset.pikchr){
        PS.render(PS.e.pikOut.dataset.pikchr);
      }
    });
    /* For each button with data-cmd=X, map a click handler which
       calls PS.render(X). */
    const cmdClick = function(){PS.render(this.dataset.cmd);};
    EAll('button[data-cmd]').forEach(
      e => e.addEventListener('click', cmdClick, false)