|
1
|
(function(F/*the fossil object*/){ |
|
2
|
"use strict"; |
|
3
|
/** |
|
4
|
Client-side implementation of the /pikchrshowcs app. Requires that |
|
5
|
the fossil JS bootstrapping is complete and that these fossil JS |
|
6
|
APIs have been installed: fossil.fetch, fossil.dom, |
|
7
|
fossil.copybutton, fossil.popupwidget, fossil.storage |
|
8
|
|
|
9
|
Maintenance funkiness note: this file is for the legacy |
|
10
|
/pikchrshowcs app, which was formerly named /pikchrshow. This |
|
11
|
file and its replacement were not renamed because the replacement |
|
12
|
impl would end up getting this file's name and cause confusion in |
|
13
|
the file history. Whether that confusion would be less than this |
|
14
|
file's name matching the _other_ /pikchrshow impl will cause more |
|
15
|
or less confusion than that remains to be seen. |
|
16
|
*/ |
|
17
|
const E = (s)=>document.querySelector(s), |
|
18
|
D = F.dom, |
|
19
|
P = F.page; |
|
20
|
|
|
21
|
P.previewMode = 0 /*0==rendered SVG, 1==pikchr text markdown, |
|
22
|
2==pikchr text fossil, 3==raw SVG. */ |
|
23
|
P.response = {/*stashed state for the server's preview response*/ |
|
24
|
isError: false, |
|
25
|
inputText: undefined /* value of the editor field at render-time */, |
|
26
|
raw: undefined /* raw response text/HTML from server */, |
|
27
|
rawSvg: undefined /* plain-text SVG part of responses. Required |
|
28
|
because the browser will convert \u00a0 to |
|
29
|
if we extract the SVG from the DOM, |
|
30
|
resulting in illegal SVG. */ |
|
31
|
}; |
|
32
|
|
|
33
|
/** |
|
34
|
If string r contains an SVG element, this returns that section |
|
35
|
of the string, else it returns falsy. |
|
36
|
*/ |
|
37
|
const getResponseSvg = function(r){ |
|
38
|
const i0 = r.indexOf("<svg"); |
|
39
|
if(i0>=0){ |
|
40
|
const i1 = r.indexOf("</svg"); |
|
41
|
return r.substring(i0,i1+6); |
|
42
|
} |
|
43
|
return ''; |
|
44
|
}; |
|
45
|
|
|
46
|
F.onPageLoad(function() { |
|
47
|
document.body.classList.add('pikchrshow'); |
|
48
|
P.e = { /* various DOM elements we work with... */ |
|
49
|
previewTarget: E('#pikchrshow-output'), |
|
50
|
previewLegend: E('#pikchrshow-output-wrapper > legend'), |
|
51
|
previewCopyButton: D.attr( |
|
52
|
D.addClass(D.button(),'copy-button'), |
|
53
|
'id','preview-copy-button' |
|
54
|
), |
|
55
|
previewModeLabel: D.label('preview-copy-button'), |
|
56
|
btnSubmit: E('#pikchr-submit-preview'), |
|
57
|
btnStash: E('#pikchr-stash'), |
|
58
|
btnUnstash: E('#pikchr-unstash'), |
|
59
|
btnClearStash: E('#pikchr-clear-stash'), |
|
60
|
cbDarkMode: E('#flipcolors-wrapper > input[type=checkbox]'), |
|
61
|
taContent: E('#content'), |
|
62
|
taPreviewText: D.textarea(20,0,true), |
|
63
|
uiControls: E('#pikchrshow-controls'), |
|
64
|
previewModeToggle: D.button("Preview mode"), |
|
65
|
markupAlignDefault: D.attr(D.radio('markup-align','',true), |
|
66
|
'id','markup-align-default'), |
|
67
|
markupAlignCenter: D.attr(D.radio('markup-align','center'), |
|
68
|
'id','markup-align-center'), |
|
69
|
markupAlignIndent: D.attr(D.radio('markup-align','indent'), |
|
70
|
'id','markup-align-indent'), |
|
71
|
markupAlignWrapper: D.addClass(D.span(), 'input-with-label') |
|
72
|
}; |
|
73
|
|
|
74
|
//////////////////////////////////////////////////////////// |
|
75
|
// Setup markup alignment selection... |
|
76
|
const alignEvent = function(ev){ |
|
77
|
/* Update markdown/fossil wiki preview if it's active */ |
|
78
|
if(P.previewMode==1 || P.previewMode==2){ |
|
79
|
P.renderPreview(); |
|
80
|
} |
|
81
|
}; |
|
82
|
P.e.markupAlignRadios = [ |
|
83
|
P.e.markupAlignDefault, |
|
84
|
P.e.markupAlignCenter, |
|
85
|
P.e.markupAlignIndent |
|
86
|
]; |
|
87
|
D.append(P.e.markupAlignWrapper, |
|
88
|
D.addClass(D.append(D.span(),"align:"), |
|
89
|
'v-align-middle')); |
|
90
|
P.e.markupAlignRadios.forEach( |
|
91
|
function(e){ |
|
92
|
e.addEventListener('change', alignEvent, false); |
|
93
|
D.append(P.e.markupAlignWrapper, |
|
94
|
D.addClass([ |
|
95
|
e, |
|
96
|
D.label(e, e.value || "left") |
|
97
|
], 'v-align-middle')); |
|
98
|
} |
|
99
|
); |
|
100
|
|
|
101
|
//////////////////////////////////////////////////////////// |
|
102
|
// Setup the preview fieldset's LEGEND element... |
|
103
|
D.append( P.e.previewLegend, |
|
104
|
P.e.previewModeToggle, |
|
105
|
'\u00a0', |
|
106
|
P.e.previewCopyButton, |
|
107
|
P.e.previewModeLabel, |
|
108
|
P.e.markupAlignWrapper ); |
|
109
|
|
|
110
|
//////////////////////////////////////////////////////////// |
|
111
|
// Trigger preview on Shift-Enter. |
|
112
|
P.e.taContent.addEventListener('keydown',function(ev){ |
|
113
|
if(ev.shiftKey && 13 === ev.keyCode){ |
|
114
|
ev.preventDefault(); |
|
115
|
ev.stopPropagation(); |
|
116
|
P.preview(); |
|
117
|
return false; |
|
118
|
} |
|
119
|
}, false); |
|
120
|
|
|
121
|
//////////////////////////////////////////////////////////// |
|
122
|
// Setup clipboard-copy of markup/SVG... |
|
123
|
F.copyButton(P.e.previewCopyButton, {copyFromElement: P.e.taPreviewText}); |
|
124
|
|
|
125
|
//////////////////////////////////////////////////////////// |
|
126
|
// Set up dark mode simulator... |
|
127
|
P.e.cbDarkMode.addEventListener('change', function(ev){ |
|
128
|
if(ev.target.checked) D.addClass(P.e.previewTarget, 'dark-mode'); |
|
129
|
else D.removeClass(P.e.previewTarget, 'dark-mode'); |
|
130
|
}, false); |
|
131
|
if(P.e.cbDarkMode.checked) D.addClass(P.e.previewTarget, 'dark-mode'); |
|
132
|
|
|
133
|
//////////////////////////////////////////////////////////// |
|
134
|
// Set up preview update and preview mode toggle... |
|
135
|
P.e.btnSubmit.addEventListener('click', ()=>P.preview(), false); |
|
136
|
P.e.previewModeToggle.addEventListener('click', function(){ |
|
137
|
/* Rotate through the 4 available preview modes */ |
|
138
|
P.previewMode = ++P.previewMode % 4; |
|
139
|
P.renderPreview(); |
|
140
|
}, false); |
|
141
|
|
|
142
|
//////////////////////////////////////////////////////////// |
|
143
|
// Set up selection list of predefined scripts... |
|
144
|
if(true){ |
|
145
|
const selectScript = P.e.selectScript = D.select(), |
|
146
|
cbAutoPreview = P.e.cbAutoPreview = |
|
147
|
D.attr(D.checkbox(true),'id', 'cb-auto-preview'), |
|
148
|
cbWrap = D.addClass(D.div(),'input-with-label') |
|
149
|
; |
|
150
|
D.append( |
|
151
|
cbWrap, |
|
152
|
selectScript, |
|
153
|
cbAutoPreview, |
|
154
|
D.label(cbAutoPreview,"Auto-preview?"), |
|
155
|
F.helpButtonlets.create( |
|
156
|
D.append(D.div(), |
|
157
|
'Auto-preview automatically previews selected ', |
|
158
|
'built-in pikchr scripts by sending them to ', |
|
159
|
'the server for rendering. Not recommended on a ', |
|
160
|
'slow connection/server.', |
|
161
|
D.br(),D.br(), |
|
162
|
'Pikchr scripts may also be dragged/dropped from ', |
|
163
|
'the local filesystem into the text area, if the ', |
|
164
|
'environment supports it, but the auto-preview ', |
|
165
|
'option does not apply to them.' |
|
166
|
) |
|
167
|
) |
|
168
|
)/*.childNodes.forEach(function(ch){ |
|
169
|
ch.style.margin = "0 0.25em"; |
|
170
|
})*/; |
|
171
|
D.append(P.e.uiControls, cbWrap); |
|
172
|
P.predefinedPiks.forEach(function(script,ndx){ |
|
173
|
const opt = D.option(script.code ? script.code.trim() :'', script.name); |
|
174
|
D.append(selectScript, opt); |
|
175
|
opt.$_sampleScript = script /* for response caching purposes */; |
|
176
|
if(!ndx) selectScript.selectedIndex = 0 /*timing/ordering workaround*/; |
|
177
|
if(!script.code) D.disable(opt); |
|
178
|
}); |
|
179
|
delete P.predefinedPiks; |
|
180
|
selectScript.addEventListener('change', function(ev){ |
|
181
|
const val = ev.target.value; |
|
182
|
if(!val) return; |
|
183
|
const opt = ev.target.selectedOptions[0]; |
|
184
|
P.e.taContent.value = val; |
|
185
|
if(cbAutoPreview.checked){ |
|
186
|
P.preview.$_sampleScript = opt.$_sampleScript; |
|
187
|
P.preview(); |
|
188
|
} |
|
189
|
}, false); |
|
190
|
} |
|
191
|
|
|
192
|
//////////////////////////////////////////////////////////// |
|
193
|
// Move dark mode checkbox to the end and add a help buttonlet |
|
194
|
D.append( |
|
195
|
P.e.uiControls, |
|
196
|
D.append( |
|
197
|
P.e.cbDarkMode.parentNode/*the .input-with-label element*/, |
|
198
|
F.helpButtonlets.create( |
|
199
|
D.div(), |
|
200
|
'Dark mode changes the colors of rendered SVG to ', |
|
201
|
'make them more visible in dark-themed skins. ', |
|
202
|
'This only changes (using CSS) how they are rendered, ', |
|
203
|
'not any actual colors written in the script.', |
|
204
|
D.br(), D.br(), |
|
205
|
'In some color combinations, certain browsers might ', |
|
206
|
'cause the SVG image to blur considerably with this ', |
|
207
|
'setting enabled!' |
|
208
|
) |
|
209
|
) |
|
210
|
); |
|
211
|
|
|
212
|
//////////////////////////////////////////////////////////// |
|
213
|
// File drag/drop pikchr scripts into P.e.taContent. |
|
214
|
// Adapted from: https://stackoverflow.com/a/58677161 |
|
215
|
const dropHighlight = P.e.taContent; |
|
216
|
const dropEvents = { |
|
217
|
drop: function(ev){ |
|
218
|
//ev.stopPropagation(); |
|
219
|
ev.preventDefault(); |
|
220
|
D.removeClass(dropHighlight, 'dragover'); |
|
221
|
const file = ev.dataTransfer.files[0]; |
|
222
|
if(file) { |
|
223
|
const reader = new FileReader(); |
|
224
|
reader.addEventListener( |
|
225
|
'load', function(e) {P.e.taContent.value = e.target.result}, false |
|
226
|
); |
|
227
|
reader.readAsText(file, "UTF-8"); |
|
228
|
} |
|
229
|
}, |
|
230
|
dragenter: function(ev){ |
|
231
|
//ev.stopPropagation(); |
|
232
|
ev.preventDefault(); |
|
233
|
ev.dataTransfer.dropEffect = "copy"; |
|
234
|
D.addClass(dropHighlight, 'dragover'); |
|
235
|
//console.debug("dragenter"); |
|
236
|
}, |
|
237
|
dragover: function(ev){ |
|
238
|
//ev.stopPropagation(); |
|
239
|
ev.preventDefault(); |
|
240
|
//console.debug("dragover"); |
|
241
|
}, |
|
242
|
dragend: function(ev){ |
|
243
|
//ev.stopPropagation(); |
|
244
|
ev.preventDefault(); |
|
245
|
//console.debug("dragend"); |
|
246
|
}, |
|
247
|
dragleave: function(ev){ |
|
248
|
//ev.stopPropagation(); |
|
249
|
ev.preventDefault(); |
|
250
|
D.removeClass(dropHighlight, 'dragover'); |
|
251
|
//console.debug("dragleave"); |
|
252
|
} |
|
253
|
}; |
|
254
|
/* |
|
255
|
The idea here is to accept drops at multiple points or, ideally, |
|
256
|
document.body, and apply them to P.e.taContent, but the precise |
|
257
|
combination of event handling needed to pull this off is eluding |
|
258
|
me. |
|
259
|
*/ |
|
260
|
[P.e.taContent |
|
261
|
//P.e.previewTarget,// works only until we drag over the SVG element! |
|
262
|
//document.body |
|
263
|
/* ideally we'd link only to document.body, but the events seem to |
|
264
|
get out of whack, with dropleave being triggered |
|
265
|
at unexpected points. */ |
|
266
|
].forEach(function(e){ |
|
267
|
Object.keys(dropEvents).forEach( |
|
268
|
(k)=>e.addEventListener(k, dropEvents[k], true) |
|
269
|
); |
|
270
|
}); |
|
271
|
|
|
272
|
//////////////////////////////////////////////////////////// |
|
273
|
// Setup stash/unstash |
|
274
|
const stashKey = 'pikchrshow-stash'; |
|
275
|
P.e.btnStash.addEventListener('click', function(){ |
|
276
|
const val = P.e.taContent.value; |
|
277
|
if(val){ |
|
278
|
F.storage.set(stashKey, val); |
|
279
|
D.enable(P.e.btnUnstash); |
|
280
|
F.toast.message("Stashed pikchr."); |
|
281
|
} |
|
282
|
}, false); |
|
283
|
P.e.btnUnstash.addEventListener('click', function(){ |
|
284
|
const val = F.storage.get(stashKey); |
|
285
|
P.e.taContent.value = val || ''; |
|
286
|
}, false); |
|
287
|
P.e.btnClearStash.addEventListener('click', function(){ |
|
288
|
F.storage.remove(stashKey); |
|
289
|
D.disable(P.e.btnUnstash); |
|
290
|
F.toast.message("Cleared pikchr stash."); |
|
291
|
}, false); |
|
292
|
F.helpButtonlets.create(P.e.btnClearStash.nextElementSibling); |
|
293
|
// If we have stashed contents, enable Unstash, else disable it: |
|
294
|
if(F.storage.contains(stashKey)) D.enable(P.e.btnUnstash); |
|
295
|
else D.disable(P.e.btnUnstash); |
|
296
|
|
|
297
|
//////////////////////////////////////////////////////////// |
|
298
|
// If we start with content, get it in sync with the state |
|
299
|
// generated by P.preview(). Normally the server pre-populates it |
|
300
|
// with an example. |
|
301
|
let needsPreview; |
|
302
|
if(!P.e.taContent.value){ |
|
303
|
P.e.taContent.value = F.storage.get(stashKey,''); |
|
304
|
needsPreview = true; |
|
305
|
} |
|
306
|
if(P.e.taContent.value){ |
|
307
|
/* Fill our "response" state so that renderPreview() can work */ |
|
308
|
P.response.inputText = P.e.taContent.value; |
|
309
|
P.response.raw = P.e.previewTarget.innerHTML; |
|
310
|
P.response.rawSvg = getResponseSvg( |
|
311
|
P.response.raw /*note that this is already in the DOM, |
|
312
|
which means that the browser has already mangled |
|
313
|
\u00a0 to , so...*/.split(' ').join('\u00a0')); |
|
314
|
if(needsPreview) P.preview(); |
|
315
|
else{ |
|
316
|
/*If it's from the server, it's already rendered, but this |
|
317
|
gets all labels/headers in sync.*/ |
|
318
|
P.renderPreview(); |
|
319
|
} |
|
320
|
} |
|
321
|
}/*F.onPageLoad()*/); |
|
322
|
|
|
323
|
/** |
|
324
|
Updates the preview view based on the current preview mode and |
|
325
|
error state. |
|
326
|
*/ |
|
327
|
P.renderPreview = function f(){ |
|
328
|
if(!f.hasOwnProperty('rxNonce')){ |
|
329
|
f.rxNonce = /<!--.+-->\r?\n?/g /*pikchr nonce comments*/; |
|
330
|
f.showMarkupAlignment = function(showIt){ |
|
331
|
P.e.markupAlignWrapper.classList[showIt ? 'remove' : 'add']('hidden'); |
|
332
|
}; |
|
333
|
f.getMarkupAlignmentClass = function(){ |
|
334
|
if(P.e.markupAlignCenter.checked) return ' center'; |
|
335
|
else if(P.e.markupAlignIndent.checked) return ' indent'; |
|
336
|
return ''; |
|
337
|
}; |
|
338
|
f.getSvgNode = function(txt){ |
|
339
|
const childs = D.parseHtml(txt); |
|
340
|
const wrapper = childs.filter((e)=>'DIV'===e.tagName)[0]; |
|
341
|
return wrapper ? wrapper.querySelector('svg.pikchr') : undefined; |
|
342
|
}; |
|
343
|
} |
|
344
|
const preTgt = this.e.previewTarget; |
|
345
|
if(this.response.isError){ |
|
346
|
D.append(D.clearElement(preTgt), D.parseHtml(P.response.raw)); |
|
347
|
D.addClass(preTgt, 'error'); |
|
348
|
this.e.previewModeLabel.innerText = "Error"; |
|
349
|
return; |
|
350
|
} |
|
351
|
D.removeClass(preTgt, 'error'); |
|
352
|
this.e.previewCopyButton.disabled = false; |
|
353
|
D.removeClass(this.e.markupAlignWrapper, 'hidden'); |
|
354
|
D.enable(this.e.previewModeToggle, this.e.markupAlignRadios); |
|
355
|
let label, svg; |
|
356
|
switch(this.previewMode){ |
|
357
|
case 0: |
|
358
|
label = "SVG"; |
|
359
|
f.showMarkupAlignment(false); |
|
360
|
D.parseHtml(D.clearElement(preTgt), P.response.raw); |
|
361
|
svg = preTgt.querySelector('svg.pikchr'); |
|
362
|
if(svg && P.response.rawSvg){ /*for copy button*/ |
|
363
|
this.e.taPreviewText.value = P.response.rawSvg; |
|
364
|
F.pikchr.addSrcView(svg); |
|
365
|
} |
|
366
|
break; |
|
367
|
case 1: |
|
368
|
label = "Markdown"; |
|
369
|
f.showMarkupAlignment(true); |
|
370
|
this.e.taPreviewText.value = [ |
|
371
|
'```pikchr'+f.getMarkupAlignmentClass(), |
|
372
|
this.response.inputText.trim(), '```' |
|
373
|
].join('\n'); |
|
374
|
D.append(D.clearElement(preTgt), this.e.taPreviewText); |
|
375
|
break; |
|
376
|
case 2: |
|
377
|
label = "Fossil wiki"; |
|
378
|
f.showMarkupAlignment(true); |
|
379
|
this.e.taPreviewText.value = [ |
|
380
|
'<verbatim type="pikchr', |
|
381
|
f.getMarkupAlignmentClass(), |
|
382
|
'">', this.response.inputText.trim(), '</verbatim>' |
|
383
|
].join(''); |
|
384
|
D.append(D.clearElement(preTgt), this.e.taPreviewText); |
|
385
|
break; |
|
386
|
case 3: |
|
387
|
label = "Raw SVG"; |
|
388
|
f.showMarkupAlignment(false); |
|
389
|
svg = f.getSvgNode(this.response.raw); |
|
390
|
if(svg){ |
|
391
|
this.e.taPreviewText.value = |
|
392
|
P.response.rawSvg || "Error extracting SVG element."; |
|
393
|
}else{ |
|
394
|
this.e.taPreviewText.value = "ERROR parsing response HTML:\n"+ |
|
395
|
this.response.raw; |
|
396
|
console.error("svg parsed HTML nodes:",childs); |
|
397
|
} |
|
398
|
D.append(D.clearElement(preTgt), this.e.taPreviewText); |
|
399
|
break; |
|
400
|
} |
|
401
|
this.e.previewModeLabel.innerText = label; |
|
402
|
this.e.taContent.focus(/*not sure why this gets lost on preview!*/); |
|
403
|
}; |
|
404
|
|
|
405
|
/** |
|
406
|
Fetches the preview from the server and updates the preview to |
|
407
|
the rendered SVG content or error report. |
|
408
|
*/ |
|
409
|
P.preview = function fp(){ |
|
410
|
if(!fp.hasOwnProperty('toDisable')){ |
|
411
|
fp.toDisable = [ |
|
412
|
/* input elements to disable during ajax operations */ |
|
413
|
this.e.btnSubmit, this.e.taContent, |
|
414
|
this.e.cbAutoPreview, this.e.selectScript, |
|
415
|
this.e.btnStash, this.e.btnClearStash |
|
416
|
/* handled separately: previewModeToggle, previewCopyButton, |
|
417
|
markupAlignRadios */ |
|
418
|
]; |
|
419
|
fp.target = this.e.previewTarget; |
|
420
|
fp.updateView = function(c,isError){ |
|
421
|
P.previewMode = 0; |
|
422
|
P.response.raw = c; |
|
423
|
P.response.rawSvg = getResponseSvg(c); |
|
424
|
P.response.isError = isError; |
|
425
|
D.enable(fp.toDisable); |
|
426
|
P.renderPreview(); |
|
427
|
}; |
|
428
|
} |
|
429
|
D.disable(fp.toDisable, this.e.previewModeToggle, this.e.markupAlignRadios); |
|
430
|
D.addClass(this.e.markupAlignWrapper, 'hidden'); |
|
431
|
this.e.previewCopyButton.disabled = true; |
|
432
|
const content = this.e.taContent.value.trim(); |
|
433
|
this.response.raw = this.response.rawSvg = undefined; |
|
434
|
this.response.inputText = content; |
|
435
|
const sampleScript = fp.$_sampleScript; |
|
436
|
delete fp.$_sampleScript; |
|
437
|
if(sampleScript && sampleScript.cached){ |
|
438
|
fp.updateView(sampleScript.cached, false); |
|
439
|
return this; |
|
440
|
} |
|
441
|
if(!content){ |
|
442
|
fp.updateView("No pikchr content!",true); |
|
443
|
return this; |
|
444
|
} |
|
445
|
const self = this; |
|
446
|
const fd = new FormData(); |
|
447
|
fd.append('ajax', true); |
|
448
|
fd.append('content',content); |
|
449
|
F.fetch('pikchrshow',{ |
|
450
|
payload: fd, |
|
451
|
responseHeaders: 'x-pikchrshow-is-error', |
|
452
|
onload: (r,isErrHeader)=>{ |
|
453
|
const isErr = +isErrHeader ? true : false; |
|
454
|
if(!isErr && sampleScript){ |
|
455
|
sampleScript.cached = r; |
|
456
|
} |
|
457
|
fp.updateView(r,isErr); |
|
458
|
}, |
|
459
|
onerror: (e)=>{ |
|
460
|
F.fetch.onerror(e); |
|
461
|
fp.updateView("Error fetching preview: "+e, true); |
|
462
|
} |
|
463
|
}); |
|
464
|
return this; |
|
465
|
}/*preview()*/; |
|
466
|
|
|
467
|
/** |
|
468
|
Predefined scripts. Each entry is an object: |
|
469
|
|
|
470
|
{ |
|
471
|
name: required string, |
|
472
|
|
|
473
|
code: optional code string. An entry with no code |
|
474
|
is treated like a separator in the resulting |
|
475
|
SELECT element (a disabled OPTION). |
|
476
|
|
|
477
|
} |
|
478
|
*/ |
|
479
|
P.predefinedPiks = [ |
|
480
|
{name: "-- Example Scripts --"}, |
|
481
|
/* |
|
482
|
The following were imported from the pikchr test scripts: |
|
483
|
|
|
484
|
https://fossil-scm.org/pikchr/dir/examples |
|
485
|
*/ |
|
486
|
{name:"Cardinal headings",code:` linerad = 5px |
|
487
|
C: circle "Center" rad 150% |
|
488
|
circle "N" at 1.0 n of C; arrow from C to last chop -> |
|
489
|
circle "NE" at 1.0 ne of C; arrow from C to last chop <- |
|
490
|
circle "E" at 1.0 e of C; arrow from C to last chop <-> |
|
491
|
circle "SE" at 1.0 se of C; arrow from C to last chop -> |
|
492
|
circle "S" at 1.0 s of C; arrow from C to last chop <- |
|
493
|
circle "SW" at 1.0 sw of C; arrow from C to last chop <-> |
|
494
|
circle "W" at 1.0 w of C; arrow from C to last chop -> |
|
495
|
circle "NW" at 1.0 nw of C; arrow from C to last chop <- |
|
496
|
arrow from 2nd circle to 3rd circle chop |
|
497
|
arrow from 4th circle to 3rd circle chop |
|
498
|
arrow from SW to S chop <-> |
|
499
|
circle "ESE" at 2.0 heading 112.5 from Center \ |
|
500
|
thickness 150% fill lightblue radius 75% |
|
501
|
arrow from Center to ESE thickness 150% <-> chop |
|
502
|
arrow from ESE up 1.35 then to NE chop |
|
503
|
line dashed <- from E.e to (ESE.x,E.y) |
|
504
|
line dotted <-> thickness 50% from N to NW chop |
|
505
|
`},{name:"Core object types",code:`AllObjects: [ |
|
506
|
|
|
507
|
# First row of objects |
|
508
|
box "box" |
|
509
|
box rad 10px "box (with" "rounded" "corners)" at 1in right of previous |
|
510
|
circle "circle" at 1in right of previous |
|
511
|
ellipse "ellipse" at 1in right of previous |
|
512
|
|
|
513
|
# second row of objects |
|
514
|
OVAL1: oval "oval" at 1in below first box |
|
515
|
oval "(tall &" "thin)" "oval" width OVAL1.height height OVAL1.width \ |
|
516
|
at 1in right of previous |
|
517
|
cylinder "cylinder" at 1in right of previous |
|
518
|
file "file" at 1in right of previous |
|
519
|
|
|
520
|
# third row shows line-type objects |
|
521
|
dot "dot" above at 1in below first oval |
|
522
|
line right from 1.8cm right of previous "lines" above |
|
523
|
arrow right from 1.8cm right of previous "arrows" above |
|
524
|
spline from 1.8cm right of previous \ |
|
525
|
go right .15 then .3 heading 30 then .5 heading 160 then .4 heading 20 \ |
|
526
|
then right .15 |
|
527
|
"splines" at 3rd vertex of previous |
|
528
|
|
|
529
|
# The third vertex of the spline is not actually on the drawn |
|
530
|
# curve. The third vertex is a control point. To see its actual |
|
531
|
# position, uncomment the following line: |
|
532
|
#dot color red at 3rd vertex of previous spline |
|
533
|
|
|
534
|
# Draw various lines below the first line |
|
535
|
line dashed right from 0.3cm below start of previous line |
|
536
|
line dotted right from 0.3cm below start of previous |
|
537
|
line thin right from 0.3cm below start of previous |
|
538
|
line thick right from 0.3cm below start of previous |
|
539
|
|
|
540
|
|
|
541
|
# Draw arrows with different arrowhead configurations below |
|
542
|
# the first arrow |
|
543
|
arrow <- right from 0.4cm below start of previous arrow |
|
544
|
arrow <-> right from 0.4cm below start of previous |
|
545
|
|
|
546
|
# Draw splines with different arrowhead configurations below |
|
547
|
# the first spline |
|
548
|
spline same from .4cm below start of first spline -> |
|
549
|
spline same from .4cm below start of previous <- |
|
550
|
spline same from .4cm below start of previous <-> |
|
551
|
|
|
552
|
] # end of AllObjects |
|
553
|
|
|
554
|
# Label the whole diagram |
|
555
|
text "Examples Of Pikchr Objects" big bold at .8cm above north of AllObjects |
|
556
|
`},{name:"Swimlanes",code:` $laneh = 0.75 |
|
557
|
|
|
558
|
# Draw the lanes |
|
559
|
down |
|
560
|
box width 3.5in height $laneh fill 0xacc9e3 |
|
561
|
box same fill 0xc5d8ef |
|
562
|
box same as first box |
|
563
|
box same as 2nd box |
|
564
|
line from 1st box.sw+(0.2,0) up until even with 1st box.n \ |
|
565
|
"Alan" above aligned |
|
566
|
line from 2nd box.sw+(0.2,0) up until even with 2nd box.n \ |
|
567
|
"Betty" above aligned |
|
568
|
line from 3rd box.sw+(0.2,0) up until even with 3rd box.n \ |
|
569
|
"Charlie" above aligned |
|
570
|
line from 4th box.sw+(0.2,0) up until even with 4th box.n \ |
|
571
|
"Darlene" above aligned |
|
572
|
|
|
573
|
# fill in content for the Alice lane |
|
574
|
right |
|
575
|
A1: circle rad 0.1in at end of first line + (0.2,-0.2) \ |
|
576
|
fill white thickness 1.5px "1" |
|
577
|
arrow right 50% |
|
578
|
circle same "2" |
|
579
|
arrow right until even with first box.e - (0.65,0.0) |
|
580
|
ellipse "future" fit fill white height 0.2 width 0.5 thickness 1.5px |
|
581
|
A3: circle same at A1+(0.8,-0.3) "3" fill 0xc0c0c0 |
|
582
|
arrow from A1 to last circle chop "fork!" below aligned |
|
583
|
|
|
584
|
# content for the Betty lane |
|
585
|
B1: circle same as A1 at A1-(0,$laneh) "1" |
|
586
|
arrow right 50% |
|
587
|
circle same "2" |
|
588
|
arrow right until even with first ellipse.w |
|
589
|
ellipse same "future" |
|
590
|
B3: circle same at A3-(0,$laneh) "3" |
|
591
|
arrow right 50% |
|
592
|
circle same as A3 "4" |
|
593
|
arrow from B1 to 2nd last circle chop |
|
594
|
|
|
595
|
# content for the Charlie lane |
|
596
|
C1: circle same as A1 at B1-(0,$laneh) "1" |
|
597
|
arrow 50% |
|
598
|
circle same "2" |
|
599
|
arrow right 0.8in "goes" "offline" |
|
600
|
C5: circle same as A3 "5" |
|
601
|
arrow right until even with first ellipse.w \ |
|
602
|
"back online" above "pushes 5" below "pulls 3 & 4" below |
|
603
|
ellipse same "future" |
|
604
|
|
|
605
|
# content for the Darlene lane |
|
606
|
D1: circle same as A1 at C1-(0,$laneh) "1" |
|
607
|
arrow 50% |
|
608
|
circle same "2" |
|
609
|
arrow right until even with C5.w |
|
610
|
circle same "5" |
|
611
|
arrow 50% |
|
612
|
circle same as A3 "6" |
|
613
|
arrow right until even with first ellipse.w |
|
614
|
ellipse same "future" |
|
615
|
D3: circle same as B3 at B3-(0,2*$laneh) "3" |
|
616
|
arrow 50% |
|
617
|
circle same "4" |
|
618
|
arrow from D1 to D3 chop |
|
619
|
`} |
|
620
|
|
|
621
|
]; |
|
622
|
|
|
623
|
})(window.fossil); |
|
624
|
|