You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
keyboard-layout-editor/kb.html

1411 lines
76 KiB
HTML

<!--***********************************************
KEYBOARD LAYOUT EDITOR
Copyright (C) 2013-2018 Ian Prest
All rights reserved.
************************************************-->
<!DOCTYPE html>
<html ng-app="kbApp">
<head>
<title>Keyboard Layout Editor</title>
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="css/bootstrap.min.css" media="screen">
<link rel="stylesheet" type="text/css" href="css/font-awesome.min.css">
<link rel="stylesheet" type="text/css" href="css/hint.min.css">
<link rel="stylesheet" type="text/css" href="css/colorpicker.min.css">
<link rel="stylesheet" type="text/css" href="css/kb.css">
<link rel="stylesheet" type="text/css" href="css/kbd-webfont.css">
<script type="text/javascript" src="js/jquery.min.js"></script>
<script type="text/javascript" src="js/ace.js"></script>
<script type="text/javascript" src="js/angular.min.js"></script>
<script type="text/javascript" src="js/angular-sanitize.min.js"></script>
<script type="text/javascript" src="js/angular-cookies.min.js"></script>
<script type="text/javascript" src="js/ui-ace.min.js"></script>
<script type="text/javascript" src="js/ui-utils.min.js"></script>
<script type="text/javascript" src="js/ui-bootstrap-tpls-0.12.0.min.js"></script>
<script type="text/javascript" src="js/crypto-js.js"></script>
<script type="text/javascript" src="js/marked.min.js"></script>
<script type="text/javascript" src="js/FileSaver.min.js"></script>
<script type="text/javascript" src="js/ng-file-upload.min.js"></script>
<script type="text/javascript" src="js/draganddrop.js"></script>
<script type="text/javascript" src="js/bootstrap-colorpicker-module.min.js"></script>
<script type="text/javascript" src="js/doT.min.js"></script>
<script type="text/javascript" src="js/urlon.js"></script>
<script type="text/javascript" src="js/cssparser.min.js"></script><script type="text/javascript">$cssParser = cssparser;</script>
<script type="text/javascript" src="js/color.js"></script>
<script type="text/javascript" src="js/jsonl.min.js"></script>
<script type="text/javascript" src="js/html2canvas.min.js"></script>
<script type="text/javascript" src="extensions.js"></script>
<script type="text/javascript" src="render.js"></script>
<script type="text/javascript" src="serial.js"></script>
<script type="text/javascript" src="kb.js"></script>
</head>
<body ng-controller="kbCtrl"
ng-mouseup="selectRelease($event)"
ng-mousemove="selectMove($event)"
ui-keydown="{'shift-191' : 'showHelp($event)',
112 : 'showHelp($event)',
118 : 'showOptions($event)',
'ctrl-83' : 'save($event)' }">
<style type="text/css" ng-bind-html="customStyles"></style>
<div id="wrap">
<div id='body_all'><!-- this wrapper is to allow the magic that prints just the summary -->
<!--***********************************************
Nav Bar / Header
************************************************-->
<nav class="navbar navbar-inverse navbar-static-top" role="navigation">
<div class="navbar-header">
<!-- Hamburger menu, when width is too small -->
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-ex1-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<!-- Main branding icon/title -->
<a class="navbar-brand" href="#"><i class="fa fa-keyboard-o"></i> keyboard-layout-editor.com</a>
</div>
<div class="collapse navbar-collapse navbar-ex1-collapse">
<!-- Left-aligned NavBar buttons -->
<ul class="nav navbar-nav">
<!-- Presets dropdown -->
<li class="dropdown" dropdown>
<a class="dropdown-toggle" dropdown-toggle><i class="fa fa-keyboard-o"></i> Preset <b class="caret"></b></a>
<ul class="dropdown-menu">
<li class="dropdown-header">Standard Layouts:</li>
<li ng-repeat="v in layouts"><a ng-click="loadPreset(v.data)" href="#">{{v.name}}</a></li>
<li class="divider"></li>
<li class="dropdown-header">Complex Samples:</li>
<li ng-repeat="(k,v) in samples"><a ng-click="loadSample(v)" href="#">{{k}}</a></li>
</ul>
</li>
<!-- Color swatches dropdown -->
<li class="dropdown" dropdown>
<a class="dropdown-toggle" dropdown-toggle><i class="fa fa-th"></i> Color Swatches <b class="caret"></b></a>
<ul class="dropdown-menu">
<li ng-repeat="pal in palettes"><a ng-click="loadPalette(pal)" href="#">{{pal.name}}</a></li>
<li class="divider"></li>
<li><a ng-click="makePaletteFromKeys()" href="#">Current key colors</a></li>
</ul>
</li>
<!-- Character Picker dropdown -->
<li class="dropdown" dropdown>
<a class="dropdown-toggle" dropdown-toggle><i class="fa fa-font"></i> Character Picker <b class="caret"></b></a>
<ul class="dropdown-menu">
<li ng-repeat="picker in pickers"><a ng-click="loadCharacterPicker(picker)" href="#">{{picker.name}}</a></li>
<li class="divider"></li>
<li><a ng-click="loadCharacterPicker(null)" href="#">User-Defined Glyphs</a></li>
</ul>
</li>
</ul>
<!-- Right-aligned NavBar buttons -->
<ul class="nav navbar-nav navbar-right" style="">
<!-- Options button -->
<li><a href="#" ng-click="showOptions()"><i class="fa fa-cog"></i> Options</a></li>
<!-- Permalink button -->
<li><a ng-href="{{getPermalink()}}" target="_blank" ng-click="dirty = false"><i class="fa fa-link"></i> Permalink</a></li>
<!-- User button -->
<li ng-hide="user"><a href="#" ng-click="userLogin()"><i class="fa fa-github"></i> Sign in with GitHub</a></li>
<li ng-hide="!user" class="dropdown" dropdown ng-cloak>
<a class="dropdown-toggle" dropdown-toggle><span ng-bind-html="user.avatar"></span> {{user.name}} <b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a ng-click="showSavedLayouts(false)" href="#"><i class="fa fa-folder-open-o"></i> My Layouts</a></li>
<li><a ng-click="showSavedLayouts(true)" href="#"><i class="fa fa-star-o"></i> Starred Layouts</a></li>
<li class="divider"></li>
<li><a href="https://github.com/settings/connections/applications/{{githubClientId}}" target="_blank"><i class="fa fa-key"></i> Manage GitHub Permissions</a></li>
<li><a ng-click="userLogout()" href="#"><i class="fa fa-sign-out"></i> Log out</a></li>
</ul>
</li>
</ul>
</div>
</nav>
<div class="body" ng-cloak>
<!--***********************************************
Main Toolbar
************************************************-->
<!-- Add Key button w/dropdown -->
<div class="btn-group" dropdown>
<button type="button" class="btn btn-primary" ng-click="addKey()"><i class="fa fa-plus-circle"></i> Add Key</button>
<button type="button" class="btn btn-primary dropdown-toggle" dropdown-toggle>
<span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">
<li><a ng-click="addKeys(1)">Add 1 Key</a></li>
<li><a ng-click="addKeys(5)">Add 5 Keys</a></li>
<li><a ng-click="addKeys(10)">Add 10 Keys</a></li>
<li><a ng-click="addKeys(25)">Add 25 Keys</a></li>
<li class="divider" ng-class="{hidden: !specialKeys}"></li>
<li ng-repeat="(k,v) in specialKeys"><a ng-click="addKey(v)">{{k}}</a></li>
</ul>
</div>
<!-- Delete Key button -->
<div class="btn-group">
<button type="button" class="btn btn-danger" ng-class="{disabled:selectedKeys.length<1}" ng-click="deleteKeys()">
<i class="fa fa-minus-circle"></i> Delete Keys
</button>
</div>
<!-- Undo/Redo button group -->
<div class="btn-group">
<button type="button" class="btn btn-default" ng-class="{disabled:!canUndo()}" ng-click="undo()"><i class="fa fa-undo"></i> Undo</button>
<button type="button" class="btn btn-default" ng-class="{disabled:!canRedo()}" ng-click="redo()"><i class="fa fa-repeat"></i> Redo</button>
</div>
<!-- Clipboard button group -->
<div class="btn-group">
<button type="button" class="btn btn-default" ng-class="{disabled:!canCopy()}" ng-click="cut()"><i class="fa fa-cut"></i> Cut</button>
<button type="button" class="btn btn-default" ng-class="{disabled:!canCopy()}" ng-click="copy()"><i class="fa fa-copy"></i> Copy</button>
<button type="button" class="btn btn-default" ng-class="{disabled:!canPaste()}" ng-click="paste()"><i class="fa fa-paste"></i> Paste</button>
</div>
<div class="btn-group" ng-hide="!user || !user.id || !currentGist || isStarred">
<button type="button" class="btn btn-default" ng-click="setGistStar(currentGist, true)" ><i class="fa fa-star-o"></i> Star</button>
</div>
<div class="btn-group" ng-hide="!user || !user.id || !currentGist || !isStarred">
<button type="button" class="btn btn-default" ng-click="setGistStar(currentGist, false)" ><i class="fa fa-star"></i> Unstar</button>
</div>
<!-- Save button (right aligned) -->
<div class="btn-group pull-right" ng-hide="user && user.id">
<button type="button" class="btn btn-success hint--bottom hint--no-animate disabled" data-hint="Sign in to save your layouts.">
<i class="fa fa-save"></i> Save
</button>
</div>
<div class="btn-group pull-right" ng-hide="!user || !user.id">
<button type="button" class="btn btn-success" ng-class="{disabled:!canSave()}" ng-click="save()">
<i class="fa fa-save"></i> Save
</button>
</div>
<div class="btn-group pull-right" style="margin-right: 4px;" dropdown>
<button type="button" class="btn btn-success dropdown-toggle" dropdown-toggle><i class="fa fa-download"></i> Download <span class="caret"></span></button>
<ul class="dropdown-menu" role="menu">
<li><a ng-click="downloadSvg()">Download SVG (Experimental)</a></li>
<li><a ng-click="downloadPng()">Download PNG (Experimental)</a></li>
<li><a ng-click="downloadJpg()">Download JPG (Experimental)</a></li>
<li><a ng-click="downloadThumb()">Download Thumbnail (PNG) (Experimental)</a></li>
<li class="divider"></li>
<li><a ng-click="downloadJson()">Download JSON</a></li>
</ul>
</div>
<!--***********************************************
Main Keyboard Preview/Editor area
************************************************-->
<div id="keyboard" ng-cloak tabindex="0"
ui-keydown="{ left:'moveKeys(-moveStep,0,$event)',
right:'moveKeys(moveStep,0,$event)',
up:'moveKeys(0,-moveStep,$event)',
down:'moveKeys(0,moveStep,$event)',
'shift-left':'sizeKeys(-sizeStep,0,$event)',
'shift-right':'sizeKeys(sizeStep,0,$event)',
'shift-up':'sizeKeys(0,-sizeStep,$event)',
'shift-down':'sizeKeys(0,sizeStep,$event)',
'pageup':'rotateKeys(-rotateStep,$event)',
'pagedown':'rotateKeys(rotateStep,$event)',
'ctrl-left':'moveCenterKeys(-moveStep,0,$event)',
'ctrl-right':'moveCenterKeys(moveStep,0,$event)',
'ctrl-up':'moveCenterKeys(0,-moveStep,$event)',
'ctrl-down':'moveCenterKeys(0,moveStep,$event)',
delete:'deleteKeys()',
insert:'addKey()',
74: 'prevKey($event)',
75: 'nextKey($event)',
'shift-74': 'prevKey($event)',
'shift-75': 'nextKey($event)',
113: 'focusEditor()',
esc: 'unselectAll()',
'ctrl-65': 'selectAll($event)',
'ctrl-67 ctrl-45': 'copy($event)',
'ctrl-88 shift-46': 'cut($event)',
'ctrl-86 shift-45': 'paste($event)',
'ctrl-90' : 'undo()',
'ctrl-shift-90' : 'redo()',
'ctrl-89' : 'redo()' }"
ng-mousedown="selectClick($event)"
ngf-drop="true" ngf-change="uploadJson($files, $event)" ngf-drag-over-class="drag-over">
<div id='keyboard-bg'
ng-attr-style="height:{{kbHeight}}px; width:{{kbWidth}}px; background-color:{{keyboard.meta.backcolor}}; border-radius: {{keyboard.meta.radii || '6px'}}; {{keyboard.meta.background.style}}">
<div ng-repeat="key in keys()"
class="key {{key.profile}}"
ng-mouseover="hoveredKey=key"
ng-mouseleave="hoveredKey=null"
ng-class="{hover: hoveredKey==key, selected: selectedKeys.indexOf(key)>=0, HOMING:key.nub}"
ng-bind-html="key.html">
</div>
</div>
<div style="line-height:1.2em; padding:3px 5px;" ng-hide="!keyboard.meta.name &amp;&amp; !keyboard.meta.author">
<a href="#" ng-click="previewNotes()">{{keyboard.meta.name}}<br/><i>{{keyboard.meta.author}}</i></a>
</div>
<div style='clear:both'></div>
{{calcKbHeight()}}
</div>
<div id="selectionRectangle" ng-style="{display:selRect.display, left:selRect.l+'px', width:selRect.w+'px', top:selRect.t+'px', height:selRect.h+'px'}"></div>
<div id="rotationCrosshairs" ng-style="{display:multi.crosshairs, left:(keyboardLeft()+multi.crosshairs_x-1)+'px', top:(keyboardTop()+multi.crosshairs_y-3)+'px'}"><i class="fa fa-crosshairs"></i></div>
<!--***********************************************
Editor controls
************************************************-->
<!-- Tab strip -->
<ul class="nav nav-tabs" ng-cloak>
<li ng-class="{active:selTab==0}"><a ng-click="selTab=0" data-toggle="tab"><i class="fa fa-edit"></i> Properties</a></li>
<li ng-class="{active:selTab==1}"><a ng-click="selTab=1" data-toggle="tab"><i class="fa fa-keyboard-o"></i> Keyboard Properties</a></li>
<li ng-class="{active:selTab==3}"><a ng-click="selTab=3" data-toggle="tab"><i class="fa fa-file-text-o"></i> Custom Styles</a></li>
<li ng-class="{active:selTab==2}"><a ng-click="selTab=2" data-toggle="tab"><i class="fa fa-code"></i> Raw data</a></li>
<li ng-class="{active:selTab==4}"><a ng-click="selTab=4" data-toggle="tab"><i class="fa fa-file-text-o"></i> Summary</a></li>
<li ng-class="{active:selTab==5}"><a ng-click="selTab=5" data-toggle="tab"><i class="fa fa-wrench"></i> Tools</a></li>
</ul>
<div id="tab-content" class='col-md-12 col-lg-12 row' ui-keydown="{esc:'focusKb()'}" ng-cloak>
<!-- PROPERTIES EDITOR -->
<div id="properties" ng-class="{hidden:selTab!=0}">
<form class="form-horizontal col-sm-8 col-md-5 col-lg-5">
<!-- Top Legend -->
<div class="form-group form-group-sm">
<label class="control-label col-md-3 col-lg-3 text-nowrap" for="labeleditor0">Top Legend:</label>
<div class="col-md-9 col-lg-9">
<div class="form-inline">
<kbd-label-editor label-index="0" hint-text="Specify a top-left legend for the keycap."></kbd-label-editor>
<kbd-label-editor label-index="1" hint-text="Specify a top-center legend for the keycap."></kbd-label-editor>
<kbd-label-editor label-index="2" hint-text="Specify a top-right legend for the keycap."></kbd-label-editor>
</div>
</div>
</div>
<!-- Center Legend -->
<div class="form-group form-group-sm">
<label class="control-label col-md-3 col-lg-3 text-nowrap" for="labeleditor3">Center Legend:</label>
<div class="col-md-9 col-lg-9">
<div class="form-inline">
<kbd-label-editor label-index="3" hint-text="Specify a center-left legend for the keycap."></kbd-label-editor>
<kbd-label-editor label-index="4" hint-text="Specify a center legend for the keycap."></kbd-label-editor>
<kbd-label-editor label-index="5" hint-text="Specify a center-right legend for the keycap."></kbd-label-editor>
</div>
</div>
</div>
<!-- Bottom Legend -->
<div class="form-group form-group-sm">
<label class="control-label col-md-3 col-lg-3 text-nowrap" for="labeleditor6">Bottom Legend:</label>
<div class="col-md-9 col-lg-9">
<div class="form-inline">
<kbd-label-editor label-index="6" hint-text="Specify a bottom-left legend for the keycap."></kbd-label-editor>
<kbd-label-editor label-index="7" hint-text="Specify a bottom-center legend for the keycap."></kbd-label-editor>
<kbd-label-editor label-index="8" hint-text="Specify a bottom-right legend for the keycap."></kbd-label-editor>
</div>
</div>
</div>
<!-- Side-Print Legend -->
<div class="form-group form-group-sm">
<label class="control-label col-md-3 col-lg-3 text-nowrap" for="labeleditor9">Front Legend:</label>
<div class="col-md-9 col-lg-9">
<div class="form-inline">
<kbd-label-editor label-index="9" hint-text="Specify a front-left legend for the keycap."></kbd-label-editor>
<kbd-label-editor label-index="10" hint-text="Specify a front-center legend for the keycap."></kbd-label-editor>
<kbd-label-editor label-index="11" hint-text="Specify a front-right legend for the keycap."></kbd-label-editor>
</div>
</div>
</div>
<!-- Legend Size -->
<div class="form-group form-group-sm">
<label class="control-label col-md-3 col-lg-3 text-nowrap" for="textSize-editor">Legend Size:</label>
<div class="col-md-9 col-lg-9">
<div class="form-inline hint--top hint--rounded"
data-hint="Specify the size for the keycap legends. Setting this value will change the size of all legends, and will replace any existing size overrides.">
<div class="form-group form-group-sm">
<input id="textSize-editor" class="form-control input-sm" style="max-width:64px" size="6" type='number' min="1" max="9"
ng-model="multi.default.textSize"
ng-change="updateMulti('textSize',-1)"
ng-blur="validateMulti('textSize',-1)"
ng-disabled="selectedKeys.length<1">
</div>
</div>
</div>
</div>
<!-- Legend Color -->
<div class="form-group form-group-sm">
<label class="control-label col-md-3 col-lg-3 text-nowrap" for="textcoloreditor">Legend Color:</label>
<div class="col-md-9 col-lg-9">
<div class="form-inline">
<kbd-color-picker picker-id="textcoloreditor" picker-position="top"
ng-model="multi.default.textColor"
ng-change="updateMulti('textColor',-1)"
ng-blur="validateMulti('textColor',-1)"
ng-disabled="selectedKeys.length<1"
hint-text="Specify the color to use for the legend text on the selected keycaps. Setting this value will change the color of all legends, and will replace any existing color overrides."
ui-on-Drop="dropSwatch($data,$event,true,-1)" drop-channel="dragColor"></kbd-color-picker>
<div class="form-group form-group-sm color-name">{{colorName(multi.default.textColor)}}</div>
</div>
<!-- Swap Colors Button -->
<div>
<div id="swap-colors"
class="hint--top hint--rounded"
data-hint="Swap the text and keycap colors.">
<button class="btn btn-default"
ng-click="swapColors()"
ng-disabled="selectedKeys.length<1 || multi.decal" >
<i class="fa fa-random"></i>
</button>
</div>
</div>
</div>
</div>
<!-- Key Color -->
<div class="form-group form-group-sm">
<label class="control-label col-md-3 col-lg-3 text-nowrap" for="coloreditor">Key Color:</label>
<div class="col-md-9 col-lg-9">
<div class="form-inline">
<kbd-color-picker picker-id="coloreditor" picker-position="top"
ng-model="multi.color"
ng-change="updateMulti('color')"
ng-blur="validateMulti('color')"
ng-disabled="selectedKeys.length<1 || multi.decal"
hint-text="Specify the color to use for the background of the selected keycaps."
ui-on-Drop="dropSwatch($data,$event,false)" drop-channel="dragColor"></kbd-color-picker>
<div class="form-group form-group-sm color-name">{{colorName(multi.color)}}</div>
</div>
</div>
</div>
<!-- Width -->
<div class="form-group form-group-sm">
<label class="control-label col-md-3 col-lg-3 text-nowrap" for="width-editor">Width:</label>
<div class="col-md-9 col-lg-9">
<div class="form-inline hint--top hint--rounded"
data-hint="Specify the primary and secondary widths for the keycap. The secondary width is used to specify the size of non-rectangular or stepped keys.">
<kbd-multi-numbox field="width" size="6" min="0.5" max="24" step='{{sizeStep}}'></kbd-multi-numbox>
<div class="form-group form-group-sm"> / </div>
<kbd-multi-numbox field="width2" size="6" min="0.5" max="24" step='{{sizeStep}}' kbd-disable="multi.decal"></kbd-multi-numbox>
</div>
<!-- Swap Sizes Button -->
<div>
<div id="swap-sizes"
class="hint--top hint--rounded"
data-hint="Swap the primary &amp; secondary sizes (affects legend positioning on oddly shaped keys).">
<button class="btn btn-default"
ng-click="swapSizes()"
ng-disabled="selectedKeys.length<1 || (multi.width == multi.width2 && multi.height == multi.height2) || multi.decal" >
<i class="fa fa-random"></i>
</button>
</div>
</div>
</div>
</div>
<!-- Height -->
<div class="form-group form-group-sm">
<label class="control-label col-md-3 col-lg-3 text-nowrap" for="height-editor">Height:</label>
<div class="col-md-9 col-lg-9">
<div class="form-inline hint--top hint--rounded"
data-hint="Specify the primary and secondary heights for the keycap. The secondary height is used to specify the size of non-rectangular or stepped keys.">
<kbd-multi-numbox field="height" size="6" min="0.5" max="24" step='{{sizeStep}}'></kbd-multi-numbox>
<div class="form-group form-group-sm"> / </div>
<kbd-multi-numbox field="height2" size="6" min="0.5" max="24" step='{{sizeStep}}' kbd-disable="multi.decal"></kbd-multi-numbox>
</div>
</div>
</div>
<!-- X -->
<div class="form-group form-group-sm">
<label class="control-label col-md-3 col-lg-3 text-nowrap" for="x-editor">X:</label>
<div class="col-md-9 col-lg-9">
<div class="form-inline hint--top hint--rounded"
data-hint="Specify the X position of the keycap. An X-offset can also be specified to indicate how non-rectangular keys should be displayed.">
<kbd-multi-numbox field="x" size="6" min="0" max="36" step='{{moveStep}}'></kbd-multi-numbox>
<div class="form-group form-group-sm"> + </div>
<kbd-multi-numbox field="x2" size="6" min="-6" max="6" step='{{moveStep}}' kbd-disable="multi.decal"></kbd-multi-numbox>
</div>
</div>
</div>
<!-- Y -->
<div class="form-group form-group-sm">
<label class="control-label col-md-3 col-lg-3 text-nowrap" for="y-editor">Y:</label>
<div class="col-md-9 col-lg-9">
<div class="form-inline hint--top hint--rounded"
data-hint="Specify the Y position of the keycap. A Y-offset can also be specified to indicate how non-rectangular keys should be displayed.">
<kbd-multi-numbox field="y" size="6" min="0" max="36" step='{{moveStep}}'></kbd-multi-numbox>
<div class="form-group form-group-sm"> + </div>
<kbd-multi-numbox field="y2" size="6" min="-6" max="6" step='{{moveStep}}' kbd-disable="multi.decal"></kbd-multi-numbox>
</div>
</div>
</div>
<!-- Rotation -->
<div class="form-group form-group-sm">
<label class="control-label col-md-3 col-lg-3 text-nowrap" for="rotation_angle-editor">Rotation:</label>
<div class="col-md-9 col-lg-9">
<div class="form-inline hint--top hint--rounded"
data-hint="Specify the angle (in degrees) by which to rotate the keycaps, and the x &amp; y coordinates of the center of rotation.">
<kbd-multi-numbox field="rotation_angle" size="3" min="-180" max="180" step='{{rotateStep}}'></kbd-multi-numbox>
<div class="form-group form-group-sm"> &#176; </div>
<kbd-multi-numbox field="rotation_x" size="6" min="0" max="36" step='{{moveStep}}'></kbd-multi-numbox>
<div class="form-group form-group-sm"> , </div>
<kbd-multi-numbox field="rotation_y" size="6" min="0" max="36" step='{{moveStep}}'></kbd-multi-numbox>
</div>
</div>
</div>
<!-- Profile / Row -->
<div class="form-group form-group-sm">
<label class="control-label col-md-3 col-lg-3 text-nowrap" for="profileeditor-search">Profile / Row:</label>
<div class="col-md-9 col-lg-9">
<div class="form-inline hint--top hint--rounded"
data-hint="Specify the profile and (optionally) the row-number of the selected keys, e.g., 'DCS R1', 'DSA', 'DSA SPACE', etc.
Supported profiles:
&nbsp;&bull; SA, DSA, DCS, OEM, CHICKLET, FLAT
Supported rows:
&nbsp;&bull; R1, R2, R3, R4, R5, SPACE">
<div class="form-group form-group-sm">
<input id="profileeditor-search" class="form-control input-sm" size="24" type='text' autocomplete="off"
ng-model="multi.profile"
ng-change="updateMulti('profile')"
ng-blur="validateMulti('profile')"
ng-disabled="selectedKeys.length<1 || multi.decal">
</div>
</div>
</div>
</div>
<!-- Keyswitch options-->
<div class="form-group form-group-sm">
<label class="control-label col-md-3 col-lg-3 text-nowrap" for="switcheditor">Switch:</label>
<div class="form-inline form-outdent col-md-9 col-lg-9">
<!-- Keyswitch mount style-->
<div class="hint--bottom hint--rounded" data-hint="Specify the mount style of switch for this key.">
<div class="btn-group btn-group-sm-form dropup" dropdown>
<button id="switcheditor" type="button" class="btn btn-default dropdown-toggle dropdown-fixedwidth" ng-disabled="selectedKeys.length<1 || multi.decal" dropdown-toggle>
<div>{{(multi.sm || meta.switchMount) ? switches[multi.sm || meta.switchMount].name : 'Mount N/A'}}</div>
<b class="caret"></b>
</button>
<ul class="dropdown-menu dropdown-select" role="menu">
<li ng-repeat="(k,v) in switches"><a ng-click="setMulti('sm',k)">{{v.name + (meta.switchMount===k ? ' (default)' : '')}}</a></li>
</ul>
</div>
</div>
<!-- Keyswitch brand-->
<div class="hint--bottom hint--rounded" data-hint="Specify the brand of key switch for this key.">
<div class="btn-group btn-group-sm-form dropup" dropdown>
<button type="button" class="btn btn-default dropdown-toggle dropdown-fixedwidth" dropdown-toggle ng-disabled="selectedKeys.length<1 || !(multi.sm || meta.switchMount) || multi.decal">
<div>{{(multi.sb || meta.switchBrand) ? switches[multi.sm || meta.switchMount].brands[multi.sb || meta.switchBrand].name : 'Brand N/A'}}</div>
<b class="caret"></b>
</button>
<ul class="dropdown-menu dropdown-select" role="menu">
<li ng-repeat="(k,v) in switches[multi.sm || meta.switchMount].brands"><a ng-click="setMulti('sb',k)">{{v.name + (meta.switchBrand===k ? ' (default)' : '')}}</a></li>
</ul>
</div>
</div>
<!-- Keyswitch type-->
<div class="hint--bottom hint--rounded" data-hint="Specify the type of key switch for this key.">
<div class="btn-group btn-group-sm-form dropup" dropdown>
<button type="button" class="btn btn-default dropdown-toggle dropdown-fixedwidth" dropdown-toggle ng-disabled="selectedKeys.length<1 || !(multi.sm || meta.switchMount) || !(multi.sb || meta.switchBrand) || multi.decal">
<div>{{(multi.st || meta.switchType) ? switches[multi.sm || meta.switchMount].brands[multi.sb || meta.switchBrand].switches[multi.st || meta.switchType].name : 'Switch N/A'}}</div>
<b class="caret"></b>
</button>
<ul class="dropdown-menu dropdown-select" role="menu">
<li ng-repeat="(k,v) in switches[multi.sm || meta.switchMount].brands[multi.sb || meta.switchBrand].switches"><a ng-click="setMulti('st',k)">{{v.name + (v.feel ? ', '+v.feel : '') + (v.weight ? ', '+v.weight.toString()+' cN' : '') + (meta.switchType===k ? ' (default)' : '')}}</a></li>
</ul>
</div>
</div>
</div>
</div>
<!-- Misc -->
<div class="form-group form-group-sm">
<label class="control-label col-md-3 col-lg-3 text-nowrap">Misc:</label>
<div class="col-md-9 col-lg-9">
<div class="form-inline">
<kbd-multi-check field="ghost" kbd-disable="multi.decal" hint-text="Specify whether the selected keys should be rendered slightly transparently; this is useful to draw attention to the other, non-ghosted keycaps.">Ghosted</kbd-multi-check>
<kbd-multi-check field="stepped" kbd-disable="multi.decal" hint-text="Specify whether the selected keys are 'stepped', i.e., part of the key is at a lower height than the rest. The secondary 'width' indicates the width of the stepped part.">Stepped</kbd-multi-check>
<kbd-multi-check field="nub" kbd-disable="multi.decal" hint-text="Specifies that the selected keys are 'homing' keys, which help the user find the home-row by touch alone.">Homing</kbd-multi-check>
<kbd-multi-check field="decal" hint-text="Specifies that the selected keys are to be treated as 'decals', i.e., purely decorative additions to the layout.">Decal</kbd-multi-check>
</div>
</div>
</div>
</form>
<!-- Color Palette -->
<div class="col-md-4 col-lg-4" ng-class="{hidden:!palette.name}">
{{palette.name}} <a ng-href='{{palette.href}}' data-hint="{{palette.description}}" class="hint--top hint--rounded" target="_blank">(more info)</a>
<ul id="swatches" ng-class="{disabled:selectedKeys.length<1}">
<li ng-repeat="color in palette.colors"
ng-style="{'background-color':color.css}"
ng-click="clickSwatch(color,$event)"
class="swatch"
ng-class="{'selected-bg': color.css === multi.color, 'selected-fg': color.css === multi.default.textColor}"
tooltip="{{color.name}}"
ui-draggable="true" drag-channel="dragColor" drag="color">
<div class='highlight fg'></div>
<div class='highlight bg'></div>
</li>
</ul>
<alert type="warning" style="margin-top:10px;" ng-hide="paletteAlertHidden" close="paletteAlertHidden = true">
<i class="fa fa-info-circle"></i> Click on a swatch to set the color of the selected key(s), or Ctrl+Click to set the text color. You can also drag color swatches to individual legends to set different colors for each one.
</alert>
</div>
<!-- Character Picker -->
<div class="col-md-6 col-lg-6" ng-class="{hidden:!picker.name}">
{{picker.name}} <a ng-if='picker.href' ng-href='{{picker.href}}' data-hint="{{picker.description}}" class="hint--top hint--rounded" target="_blank">(more info)</a>
<div class="col-md-12 col-lg-12" style="padding:4px 0px;">
<input id="picker-filter" class="form-control input-sm" type='text' ng-model="pickerFilter" placeholder="Filter"
ng-change="pickerSelection = {}">
</div>
<div id="glyphScroller">
<ul id="glyphs" ng-class="{disabled:selectedKeys.length<1}">
<li ng-repeat="glyph in picker.glyphs | filter:pickerFilter"
class="glyph"
tooltip="{{glyph.name}}"
ng-click="pickerSelect(glyph)"
ng-bind-html="glyph.html"
ng-class="{selected:pickerSelection===glyph}"
ui-draggable="true" drag-channel="dragGlyph" drag="glyph">
</li>
</ul>
</div>
<div class="col-md-12 col-lg-12" style="padding:4px 0px;">
<input id="picker-html" class="form-control input-sm" type='text'
ng-model="pickerSelection.html" placeholder="No glyph selected"
onClick="this.select();" readonly>
</div>
<alert type="warning" style="margin-top:40px;" ng-hide="pickerAlertHidden" close="pickerAlertHidden = true">
<i class="fa fa-info-circle"></i> Click on a glyph to see the HTML code for that glyph. You can also drag glyphs to the legend edit-boxes to assign them to the legend.
</alert>
</div>
</div>
<!-- KEYBOARD PROPERTIES EDITOR -->
<div id="kbdproperties" ng-class="{hidden:selTab!=1}">
<form class="form-horizontal col-sm-12 col-md-12 col-lg-12">
<!-- Keyboard Background -->
<div class="form-group form-group-sm">
<label class="control-label col-md-2 col-lg-1 text-nowrap" for="kbdcoloreditor">Case Background:</label>
<div class="col-md-10 col-lg-11">
<div class="form-inline">
<!-- Color -->
<kbd-color-picker picker-id="kbdcoloreditor"
ng-model="meta.backcolor"
ng-change="updateMeta('backcolor')"
ng-blur="validateMeta('backcolor')"
hint-text="Specify the background color for the keyboard."></kbd-color-picker>
<!-- Background image-->
<div class="form-group form-group-sm">
<div class="input-group input-group-sm hint--top hint--rounded" data-hint="Specify the background texture for the keyboard.">
<div class="btn-group btn-group-sm-form" dropdown>
<button type="button" class="btn btn-default dropdown-toggle" dropdown-toggle>{{meta.background ? meta.background.name : 'No Texture'}} <b class="caret"></b></button>
<ul class="dropdown-menu dropdown-select" role="menu">
<li><a ng-click='setBackground()'>None</a></li>
<li class="divider"></li>
<li ng-repeat="category in backgrounds">
<div class="dropdown-header" ng-attr-style="{{category.style}}">{{category.category}}:</div>
<ul class="menu">
<li ng-repeat="bg in category.content"><a ng-click='setBackground(bg)'>{{bg.name}}</a></li>
</ul>
</li>
</ul>
</div>
</div>
</div>
<!-- Corner radii -->
<div class="form-group form-group-sm">
<label class="control-label text-nowrap" style='margin-left: 5px;' for="kbdradiieditor">Radii:</label>
<div class="hint--top hint--rounded"
data-hint="Specify the corner radii for this keyboard, in CSS3 format.
Some examples:
&nbsp;&bull; 20px /*all corners rounded equally*/
&nbsp;&bull; 10px 10px 10px 10px /*ditto, but less rounding*/
&nbsp;&bull; 10px 10px 10px 10px / 20px 20px 20px 20px
&nbsp;&bull; 40% / 20px
&nbsp;&bull; 30px 30px / 40%">
<input id="kbdradiieditor" class="form-control input-sm" type='text'
ng-model="meta.radii"
ng-change="updateMeta('radii')"
ng-blur="validateMeta('radii')">
</div>
</div>
</div>
</div>
</div>
<!-- Keyboard Name -->
<div class="form-group form-group-sm">
<label class="control-label col-md-2 col-lg-1 text-nowrap" for="kbdnameeditor">Keyboard Name:</label>
<div class="col-md-10 col-lg-11 form-outdent hint--top hint--rounded"
data-hint="Specify the name of this keyboard layout.">
<input id="kbdnameeditor" class="form-control input-sm" type='text'
ng-model="meta.name"
ng-change="updateMeta('name')"
ng-blur="validateMeta('name')">
</div>
</div>
<!-- Author -->
<div class="form-group form-group-sm">
<label class="control-label col-md-2 col-lg-1 text-nowrap" for="authoreditor">Author:</label>
<div class="col-md-10 col-lg-11 form-outdent hint--top hint--rounded"
data-hint="Specify the author of this keyboard layout.">
<input id="authoreditor" class="form-control input-sm" type='text'
ng-model="meta.author"
ng-change="updateMeta('author')"
ng-blur="validateMeta('author')">
</div>
</div>
<!-- Default keyswitch options-->
<div class="form-group form-group-sm">
<label class="control-label col-md-2 col-lg-1 text-nowrap" for="defaultswitcheditor">Default switch:</label>
<!-- Default keyswitch mount style-->
<div class="form-inline form-outdent col-md-10 col-lg-11">
<div class="hint--top hint--rounded" data-hint="Specify the default mount style of switch for this keyboard layout.">
<select id="defaultswitcheditor" class="form-control input-sm dropdown"
ng-model="meta.switchMount"
ng-change="updateMeta('switchMount')"
ng-blur="validateMeta('switchMount')">
<option value="">Mount Style Not Specified</option>
<option ng-repeat="(k,v) in switches" value="{{k}}" ng-selected="meta.switchMount == k">{{v.name}}</option>
</select>
</div>
<!-- Default keyswitch brand-->
<div class="hint--top hint--rounded"
data-hint="Specify the default brand of key switch for this keyboard layout.">
<select class="form-control input-sm dropdown"
ng-model="meta.switchBrand"
ng-change="updateMeta('switchBrand')"
ng-blur="validateMeta('switchBrand')"
ng-disabled="!meta.switchMount">
<option value="">Brand Not Specified</option>
<option ng-repeat="(k,v) in switches[meta.switchMount].brands" value="{{k}}" ng-selected="meta.switchBrand == k">{{v.name}}</option>
</select>
</div>
<!-- Default keyswitch type-->
<div class=" hint--top hint--rounded"
data-hint="Specify the default type of key switch for this keyboard layout.">
<select class="form-control input-sm dropdown"
ng-model="meta.switchType"
ng-change="updateMeta('switchType')"
ng-blur="validateMeta('switchType')"
ng-disabled="!meta.switchMount || !meta.switchBrand">
<option value="">Switch Type Not Specified</option>
<option ng-repeat="v in switches[meta.switchMount].brands[meta.switchBrand].switches" value="{{v.part}}" ng-selected="meta.switchType == v.part">{{v.name + (v.feel ? ', '+v.feel : '') + (v.weight ? ', '+v.weight.toString()+' cN' : '')}}</option>
</select>
</div>
<!-- Switch mounting -->
<div class="form-group form-group-sm form-horizontal">
<label class="control-label text-nowrap" style='margin-left: 5px;' for="pcb">Mounted on:</label>
<div class="checkbox hint--top hint--rounded" data-hint="Specify if the switches are PCB mounted.">
<label style='margin-bottom: -3px;'>
<input type="checkbox" id="pcb" class="checkbox"
ng-model="meta.pcb"
ng-change="updateMeta('pcb')"
ng-blur="validateMeta('pcb')">
PCB
</label>
</div>
<div class="checkbox hint--top hint--rounded" data-hint="Specify if the switches are plate mounted.">
<label style='margin-bottom: -3px;'>
<input type="checkbox" id="plate" class="checkbox"
ng-model="meta.plate"
ng-change="updateMeta('plate')"
ng-blur="validateMeta('plate')">
Plate
</label>
</div>
</div>
</div>
</div>
<!-- Notes -->
<div class="form-group form-group-sm">
<label class="control-label col-md-2 col-lg-1 text-nowrap" for="noteseditor">Notes:</label>
<div class="col-md-10 col-lg-11 form-outdent">
<div class="hint--top hint--rounded" style="width:100%"
data-hint="Specify any additional notes about this keyboard layout, e.g., links to manufacturers, colors used, additional credits, etc. GitHub-Flavored Markdown is supported for rich formatting.">
<div id="noteseditor" class="form-control"
ui-ace="{mode:'markdown',theme:'textmate',onLoad:aceLoaded,useWrapMode:true,rendererOptions:{animatedScroll:true,showPrintMargin:false}}"
ng-model="meta.notes"
ng-change="updateMeta('notes')"
ng-blur="validateMeta('notes')"></div>
</div>
<div class="text-right">
<div class="btn-group btn-group-xs" role="group">
<button type="button" class="btn btn-success" ng-click="previewNotes()"><i class="fa fa-eye"></i> Preview</button>
</div>
<div class="btn-group btn-group-xs" role="group">
<a class="btn btn-primary" href="https://help.github.com/articles/markdown-basics/" target='_blank'><i class="fa fa-question-circle"></i> Markdown Syntax</a>
</div>
</div>
</div>
</div>
</form>
</div>
<!-- CUSTOM STYLES EDITOR -->
<div id="tab-customstyles" ng-class="{hidden:selTab!=3}">
<div ngf-drop="true" ngf-change="uploadJson($files, $event)" ngf-drag-over-class="drag-over">
<div id="customstyles"
ui-ace="{mode:'css',theme:'textmate',onLoad:aceLoaded,useWrapMode:true,rendererOptions:{animatedScroll:true,showPrintMargin:false}}"
ng-model="meta.css"
ng-change="updateCustomStyles()"
ng-class="{error:customStylesException!==''}"></div>
</div>
<div style='text-align:right;margin-top:4px;'>
<div class="btn-group btn-group-xs" role="group">
<a class="btn btn-primary" href="https://github.com/ijprest/keyboard-layout-editor/wiki/Custom-Styles" target='_blank'><i class="fa fa-question-circle"></i> Custom Styles Syntax</a>
</div>
</div>
<div id="customstyles-error" class="alert alert-danger" style='margin-top:1em;margin-bottom:0px' ng-class="{hidden:!customStylesException}">{{customStylesException}}</div>
</div>
<!-- RAW DATA EDITOR -->
<div id="tab-rawdata" ng-class="{hidden:selTab!=2}">
<div ngf-drop="true" ngf-change="uploadJson($files, $event)" ngf-drag-over-class="drag-over">
<div id="rawdata"
ui-ace="{mode:'json',theme:'textmate',onLoad:aceLoaded,useWrapMode:true,rendererOptions:{animatedScroll:true,showPrintMargin:false}}"
ng-model="serialized"
ng-change="updateFromSerialized()"
ng-class="{error:deserializeException!==''}"></div>
</div>
<div style='text-align:right;margin-top:4px;'>
<div class="btn-group btn-group-xs" role="group">
<button type="button" class="btn btn-success" ng-click="downloadJson()"><i class="fa fa-download"></i> Download JSON</button>
<button ngf-select="true" class="btn btn-success" ngf-change="uploadJson($files, $event)"><i class="fa fa-upload"></i> Upload JSON</button>
</div>
<div class="btn-group btn-group-xs" role="group">
<a class="btn btn-primary" href="https://github.com/ijprest/keyboard-layout-editor/wiki/Serialized-Data-Format" target='_blank'><i class="fa fa-question-circle"></i> Raw Data Syntax</a>
</div>
</div>
<div id="rawdata-error" class="alert alert-danger" style='margin-top:1em;margin-bottom:0px' ng-class="{hidden:!deserializeException}">{{deserializeException}}</div>
</div>
<!-- SUMMARY -->
<div id="summary" ng-class="{hidden:selTab!=4}">
<div class="col-md-12 col-lg-12 row">
<div id='printSummary'>
<div>Title: {{meta.name}}</div>
<div>Author: {{meta.author}}</div>
<table class='summarytable'>
<tr><th class='summarytable' colspan='3'>Key sizes summary</th></tr>
<tr ng-repeat="(k,v) in keyCount()" class='summarytable'>
<td>{{k}}</td>
<td><span class="fa fa-square" ng-style="{'color':getTextColor(k)}">&nbsp;</span></td>
<td align='right'>{{v}}</td>
</tr>
</table>
<table class='summarytable'>
<tr><th colspan='3'>Switches summary</th></tr>
<tr ng-repeat="(k,v) in switchCount()"><td>{{k}}</td><td>{{switchNames[k]}}</td><td>{{v}}</td></tr>
</table>
</div>
</div>
<div class="btn-group btn-group-xs" role="group">
<br><button type="button" class="btn btn-success" ng-click="printDiv('printSummary')"><i class="fa fa-print"></i> Print summary</button>
</div>
</div>
<div id="tools" ng-class="{hidden:selTab!=5}">
<div class="col-md-4 col-lg-4 row">
<h5>Remove Legends:</h5>
<div class="form-group form-group-sm form-horizontal">
<div ng-repeat="button in removeLegendsButtons" class="btn-group btn-group-xs" style="margin-right: 3px;" role="group">
<button type="button" class="btn btn-success hint--top hint--rounded" data-hint="{{button.tooltip}}" ng-click="removeLegends(button)">{{button.label}}</button>
</div>
</div>
<h5>Misc:</h5>
<div class="btn-group btn-group-xs">
<button type="button" class="btn btn-success hint--top hint--rounded" data-hint="Convert decals back into normal keys." ng-click="unhideDecals()"> Unhide decals</button>
</div>
</div>
<div class="col-md-2 col-lg-2 row">
<h5>Align Legends:</h5>
<div class="keycap" id="text-align">
<div class="keyborder"></div>
<div class="keylabels">
<div ng-repeat="(i,button) in alignLegendsButtons" class="keylabel keylabel{{i}}">
<div class="btn-group btn-group-xs">
<button type="button" class="btn btn-success" ng-click="alignLegends(button.flags)" ng-bind-html="button.label"></button>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-4 col-lg-4 row">
<h5>Move Legends:</h5>
<form>
<div class="keycap" id="label-move-src">
<div class="keyborder"></div>
<div class="keylabels">
<div ng-repeat="i in [0,1,2,3,4,5,6,7,8,9,10,11]" class="keylabel keylabel{{i}}">
<div><input type='radio' name='fromId' ng-model="$parent.moveFromId" ng-value='i'></div>
</div>
</div>
</div>
<div style="float:left;margin-top:40px;">&nbsp;<i class="fa fa-arrow-right"></i>&nbsp;</div>
<div class="keycap" id="label-move-dst">
<div class="keyborder"></div>
<div class="keylabels">
<div ng-repeat="i in [0,1,2,3,4,5,6,7,8,9,10,11]" class="keylabel keylabel{{i}}">
<div><input type='radio' name='toId' ng-model="$parent.moveToId" ng-value='i'></div>
</div>
</div>
</div>
&nbsp;
<button type="button" class="btn btn-success hint--top hint--rounded" data-hint="Move the legends in the From spot to the To spot." ng-click="moveSingleLegends()">Go</button>
</form>
</div>
<div class="col-md-4 col-lg-4 row">
<alert type="warning" style="margin-top:10px; margin-right:15px;" ng-hide="toolsAlertHidden" close="toolsAlertHidden = true">
<i class="fa fa-info-circle"></i> All tools will affect the selected keys only; if no keys are selected, tools will affect all keys.
</alert>
</div>
</div>
</div> <!-- END OF TAB CONTENT -->
<div class="row" style="margin-top:1em;margin-left:0px;">
<div class="col-md-12 col-lg-12">
<alert type="success" ng-hide="!saved" close="saved=false" ng-cloak>
Saved! Your saved layout can be accessed via <a class="alert-link" ng-href="#/gists/{{saved}}">this link</a>.
</alert>
<alert type="danger" ng-hide="!saveError" close="saveError=''" ng-cloak>
<P>There was an error saving your layout on the server: {{saveError}}</P>
<P>To ensure you don't lose any data, it is recommended that you <a class="alert-link" ng-click="downloadJson()">download your layout as JSON</a>.</P>
</alert>
<alert type="danger" ng-hide="!loadError" close="loadError=false" ng-cloak>
The requested layout does not exist.
</alert>
<alert type="danger" ng-hide="!oauthError" close="oauthError=null" ng-cloak>
An error occurred during login: {{oauthError}}
</alert>
</div>
</div>
</div> <!-- body -->
</div> <!-- body_all -->
</div> <!-- wrap -->
<!--***********************************************
Footer
************************************************-->
<div id="footer" ng-cloak>
<div class="container">
<div class="text-muted credit" style="float:left">
Keyboard Layout Editor v{{version}} (<a href='#' ng-click="showMarkdown('CHANGELOG.md', $event)">changelog</a>)<br/>
Copyright &copy; 2013-2018 &mdash; Ian Prest (<a href='#' ng-click="showMarkdown('CONTRIB.md', $event)">and contributors</a>)<br/>
All rights reserved. (<a href='#' ng-click="showMarkdown('LICENSE.md', $event)">LICENSE</a>)
</div>
<div style="float:right;">
<a href="#" ng-click="showHelp($event)"><i class="fa fa-question-circle"></i> Help &amp; keyboard shortcuts</a><br/>
<a href="https://github.com/ijprest/keyboard-layout-editor/issues" target="_blank"><i class="fa fa-bug"></i> Found a bug?</a><br/>
<a href="https://github.com/ijprest/keyboard-layout-editor" target="_blank"><i class="fa fa-github"></i> Code hosted on GitHub</a><br/>
</div>
</div>
</div>
<!--***********************************************
Help Dialog
************************************************-->
<script type="text/ng-template" id="helpDialog.html">
<div class="modal-header">
<button type="button" class="close" ng-click="cancel()">&times;</button>
<h4 class="modal-title">How to use keyboard-layout-editor.com</h4>
</div>
<div class="modal-body">
<h5>Getting Started:</h5>
<ul>
<li>Try loading one of the preset layouts from the dropdown at the top of the page.</li>
<ul>
<li>The <em>standard layouts</em> are intended to be used as a starting point for customization.</li>
<li>The <em>complex samples</em> are intended to give you an idea of the editor's capabilities, and to show off some awesome user-creations!</li>
</ul>
<li>Select a key in the editor by clicking on it; then try changing the various properties in the property-editor form.</li>
<li>You can select multiple keys by holding down CTRL and clicking on them, or extend the current selection by SHIFT-clicking on an item. You can also drag a marquee rectangle around the keys you want to select.</li>
<li>Try the keyboard shortcuts (described below); they make editing various properties much quicker.</li>
<li>You can save your layout to the server by clicking the 'Save' button on the toolbar.</li>
<li>You can save your layout locally by bookmarking the 'Permalink' (at the top of the page) in your bookmarks list.</li>
</ul>
<h5>Global Keyboard Shortcuts:</h5>
<dl class='dl-horizontal'>
<dt><kbd>F1</kbd> / <kbd>?</kbd></dt><dd>Show this help dialog</dd>
<dt><kbd>F7</kbd></dt><dd>Show the Options dialog</dd>
<dt><kbd>Ctrl&ndash;S</kbd></dt><dd>Save your layout (on the server)</dd>
</dl>
<h5>Editor Keyboard Shortcuts (these require that the editor has input focus):</h5>
<dl class='dl-horizontal' style='float:left;width:50%;'>
<dt><kbd></kbd></dt><dd>Move the selected keys</dd>
<dt><kbd>Shift&ndash;</kbd></dt><dd>Resize the selected keys</dd>
<dt><kbd>Ctrl&ndash;</kbd></dt><dd>Move the center of rotation for the selected keys</dd>
<dt><kbd>PgUp</kbd> / <kbd>PgDn</kbd></dt><dd>Change the angle of rotation for the selected keys</dd>
<dt><kbd>Ins</kbd></dt><dd>Add a new key</dd>
<dt><kbd>Del</kbd></dt><dd>Delete the selected keys</dd>
<dt><kbd>F2</kbd></dt><dd>Edit the text of the selected key</dd>
</dl>
<dl class='dl-horizontal' style='float:left;width:50%;'>
<dt><kbd>Ctrl&ndash;A</kbd> / <kbd>Esc</kbd></dt><dd>Select / deselect all keys</dd>
<dt><kbd>J</kbd> / <kbd>K</kbd></dt><dd>Select the previous / next key in the editor.</dd>
<dt><kbd>Ctrl&ndash;Z</kbd></dt><dd>Undo</dd>
<dt><kbd>Ctrl&ndash;Shift&ndash;Z</kbd> or <kbd>Ctrl&ndash;Y</kbd></dt><dd>Redo</dd>
<dt><kbd>Ctrl&ndash;X</kbd> or <kbd>Shift&ndash;Del</kbd></dt><dd>Cut the selected keys</dd>
<dt><kbd>Ctrl&ndash;C</kbd> or <kbd>Ctrl&ndash;Ins</kbd></dt><dd>Copy the selected keys</dd>
<dt><kbd>Ctrl&ndash;V</kbd> or <kbd>Shift&ndash;Ins</kbd></dt><dd>Paste keys from the clipboard</dd>
</dl>
<p>&nbsp;</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" ng-click="ok()">OK</button>
</div>
</script>
<!--***********************************************
Markdown dialog for displaying README, etc.
************************************************-->
<script type="text/ng-template" id="markdownDialog.html">
<div class="modal-header">
<button type="button" class="close" ng-click="cancel()">&times;</button>
<h4 class="modal-title">{{$parent.markdownTitle}}</h4>
</div>
<div class="modal-body" ng-bind-html="$parent.markdownContent">&nbsp;</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" ng-click="ok()">OK</button>
</div>
</script>
<!--***********************************************
Options Dialog
************************************************-->
<script type="text/ng-template" id="optionsDialog.html">
<div class="modal-header">
<button type="button" class="close" ng-click="cancel()">&times;</button>
<h4 class="modal-title">Options</h4>
</div>
<div class="modal-body">
<h5>Editor Step Sizes:</h5>
<div class="form-horizontal">
<div class="form-group form-group-sm">
<label for="moveStepEditor" class="col-sm-6 col-md-3 col-lg-2 control-label">Move Step:</label>
<div class="col-sm-6 col-md-9 col-lg-10">
<div class="form-inline">
<div class="form-group form-group-sm">
<div class="input-group input-group-sm hint--top hint--rounded"
data-hint="Controls the step size when moving keys around the keyboard layout.">
<input id="moveStepEditor" class="form-control" type="number" min="0.05" max="2.5" step="0.05" size="6" ng-model="params.moveStep">
<span class="input-group-addon">units</span>
</div>
</div>
</div>
</div>
</div>
<div class="form-group form-group-sm">
<label for="sizeStepEditor" class="col-sm-6 col-md-3 col-lg-2 control-label">Size Step:</label>
<div class="col-sm-6 col-md-9 col-lg-10">
<div class="form-inline">
<div class="form-group form-group-sm">
<div class="input-group input-group-sm hint--top hint--rounded"
data-hint="Controls the step size when resizing keys in the keyboard layout.">
<input id="sizeStepEditor" class="form-control" type="number" min="0.05" max="2.5" step="0.05" size="6" ng-model="params.sizeStep">
<span class="input-group-addon">units</span>
</div>
</div>
</div>
</div>
</div>
<div class="form-group form-group-sm">
<label for="rotateStepEditor" class="col-sm-6 col-md-3 col-lg-2 control-label">Rotate Step:</label>
<div class="col-sm-6 col-md-9 col-lg-10">
<div class="form-inline">
<div class="form-group form-group-sm">
<div class="input-group input-group-sm hint--top hint--rounded"
data-hint="Controls the step size when rotating keys in the keyboard layout.">
<input id="rotateStepEditor" class="form-control" type="number" min="1" max="90" step="1" size="6" ng-model="params.rotateStep">
<span class="input-group-addon">degrees</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger" ng-click="cancel()">Cancel</button>
<button type="button" class="btn btn-primary" ng-click="ok()">OK</button>
</div>
</script>
<!--***********************************************
Markdown dialog for displaying README, etc.
************************************************-->
<script type="text/ng-template" id="savedLayouts.html">
<div class="modal-header">
<button type="button" class="close" ng-click="cancel()">&times;</button>
<h4 class="modal-title">{{params.starred ? 'Starred Layouts' : 'Saved Layouts'}}</h4>
</div>
<div class="modal-body">
<table class="table table-hover">
<thead>
<tr>
<th>#</th>
<th>Name</th>
<th>Date</th>
<th>&nbsp;</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
<tr ng-if="layouts.length === 0">
<td colspan='5'>{{params.starred ? 'No starred layouts.' : 'No saved layouts.'}}</td>
</tr>
<tr ng-repeat="layout in layouts">
<td>{{layout.index}}</td>
<td>{{layout.description}}</td>
<td>{{layout.updated_at}}</td>
<td nowrap>
<a class="btn btn-danger btn-xs" href="#" ng-click="delete(layout.id)" ng-hide="params.starred"><i class="fa fa-trash"></i> Delete</a>
<a class="btn btn-primary btn-xs" ng-href="{{layout.html_url}}" target='_blank'><i class="fa fa-github"></i> View on GitHub</a>
</td>
<td nowrap>
<a class="btn btn-success btn-xs" href="#" ng-click="load(layout.id)"><i class="fa fa-folder-open"></i> Load</a>
</td>
</tr>
</tbody>
</table>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" ng-click="ok()">OK</button>
</div>
</script>
<!--***********************************************
Angular Templates
************************************************-->
<!-- Color Picker Template -->
<script type="text/ng-template" id="colorPicker.html">
<div class="form-group form-group-sm">
<div class="input-group input-group-sm hint--top hint--rounded" ng-attr-data-hint="{{hintText}}">
<input id="{{pickerId}}" class="form-control" size="8" type='text'
ng-model="color" ng-change="onChange()" ng-blur="onBlur()" ng-disabled="isDisabled &amp;&amp; isDisabled()"
colorpicker="hex" colorpicker-close-on-select="true" colorpicker-position="{{pickerPosition || 'bottom'}}">
<span class="input-group-btn">
<button class="btn btn-default" ng-style="{'background-color':color}" ng-model="color" ng-change="onChange()" ng-disabled="isDisabled &amp;&amp; isDisabled()"
colorpicker="hex" colorpicker-close-on-select="true" colorpicker-position="{{pickerPosition || 'bottom'}}">&nbsp;</button>
</span>
</div>
</div>
</script>
<!-- Simple Editor Templates -->
<script type="text/ng-template" id="multiCheck.html">
<div class="form-group form-group-sm">
<div class="checkbox hint--top hint--rounded" ng-attr-data-hint="{{hintText}}">
<label>
<input id="{{field}}" type="checkbox"
ng-model="$parent.multi[field]"
ng-change="$parent.updateMulti(field)"
ng-blur="$parent.validateMulti(field)"
ng-disabled="$parent.selectedKeys.length<1 || kbdDisable">
<span ng-transclude></span>
</label>
</div>
</div>
</script>
<script type="text/ng-template" id="multiNumbox.html">
<div class="form-group form-group-sm">
<input id="{{field}}-editor" class="form-control input-sm" size="{{size}}" type='number' min="{{min}}" max="{{max}}" step="{{step || 1}}"
ng-model="$parent.multi[field]"
ng-change="$parent.updateMulti(field)"
ng-blur="$parent.validateMulti(field)"
ng-disabled="$parent.selectedKeys.length<1 || kbdDisable"
style="max-width:80px">
</div>
</script>
<!-- Label Editor Template -->
<script type="text/ng-template" id="labelEditor.html">
<div class="form-group form-group-sm">
<div class="input-group input-group-sm hint--top hint--rounded" ng-attr-data-hint="{{hintText}}"
ui-on-Drop="$parent.dropSwatch($data,$event,true,labelIndex)" drop-channel="dragColor">
<input id="labeleditor{{labelIndex}}" class="form-control" size="6" type='text'
ng-model="$parent.multi.labels[labelIndex]"
ng-change="$parent.updateMulti('labels',labelIndex)"
ng-blur="$parent.validateMulti('labels',labelIndex)"
ng-disabled="$parent.selectedKeys.length<1"
ui-on-Drop="$parent.dropGlyph($data,$event,labelIndex)" drop-channel="dragGlyph">
<span class="input-group-btn">
<button class="btn btn-default"
ng-style="{'background': $parent.selectedKeys.length<1 ? '#eee' : (!$parent.multi.textColor[labelIndex] ? '#fff' : $parent.multi.textColor[labelIndex])}"
ng-model="$parent.multi.textColor[labelIndex]"
ng-change="$parent.updateMulti('textColor',labelIndex)"
colorpicker="hex"
colorpicker-close-on-select="true"
colorpicker-with-input="true"
ng-class="{'use-default':!$parent.multi.textColor[labelIndex]}"
ng-disabled="$parent.selectedKeys.length<1">&nbsp;</button>
</span>
<input id="textsizeeditor{{labelIndex}}" class="form-control input-sm" size="4" type='number' min="1" max="9"
ng-model="$parent.multi.textSize[labelIndex]"
ng-change="$parent.updateMulti('textSize',labelIndex)"
ng-blur="$parent.validateMulti('textSize',labelIndex)"
ng-disabled="$parent.selectedKeys.length<1"
ng-style="{visibility:labelIndex<9 ? 'inherit' : 'hidden'}"
style="max-width:32px;">
</div>
</div>
</script>
<!--***********************************************
DOT.js Templates
************************************************-->
<!-- Keycap Template -->
<script type="text/ng-template" id="keycap_html">
<div class='{{=key.ghost ? "ghosted" : ""}} {{=key.decal ? "decal" : ""}} keycap'
{{? key.rotation_angle }}
style='transform:rotate({{=key.rotation_angle}}deg); transform-origin:{{=parms.origin_x}}px {{=parms.origin_y}}px;'
{{?}}>
<div style="left: {{=parms.outercapx}}px; top: {{=parms.outercapy}}px;
width: {{=parms.outercapwidth}}px; height: {{=parms.outercapheight}}px;
border-width: {{=sizes.strokeWidth}}px; border-radius: {{=sizes.roundOuter}}px;
background-color: {{=parms.darkColor}};"
class="keyborder"></div>
{{? parms.jShaped}}
<div style="left: {{=parms.outercapx2}}px; top: {{=parms.outercapy2}}px;
width: {{=parms.outercapwidth2}}px; height: {{=parms.outercapheight2}}px;
border-width: {{=sizes.strokeWidth}}px; border-radius: {{=sizes.roundOuter}}px;
background-color: {{=parms.darkColor}};"
class="keyborder"></div>
<div style="left: {{=parms.outercapx + sizes.strokeWidth}}px; top: {{=parms.outercapy + sizes.strokeWidth}}px;
width: {{=parms.outercapwidth - sizes.strokeWidth*2}}px;
height: {{=parms.outercapheight - sizes.strokeWidth*2}}px;
background-color: {{=parms.darkColor}};
border-radius: {{=sizes.roundOuter}}px;"></div>
{{?}}
{{? !key.ghost }}
{{? !key.decal}}
<div style="left: {{=parms.innercapx}}px; top: {{=parms.innercapy}}px;
width: {{=parms.innercapwidth}}px; height: {{=parms.innercapheight}}px;
border-style: solid; border-width: {{=sizes.strokeWidth}}px; border-color: rgba(0,0,0,0.1);
background-color: {{=parms.lightColor}};
border-radius: {{=sizes.roundInner}}px;"
class="keytop"></div>
{{? parms.jShaped && !key.stepped }}
<div style="left: {{=parms.innercapx2}}px; top: {{=parms.innercapy2}}px;
width: {{=parms.innercapwidth2}}px; height: {{=parms.innercapheight2}}px;
border-style: solid; border-width: {{=sizes.strokeWidth}}px; border-color: rgba(0,0,0,0.1); border-radius: {{=sizes.roundInner}}px;
background-color: {{=parms.lightColor}};
background-position: {{=Math.min(parms.innercapx-parms.innercapx2,0)}}px {{=Math.min(parms.innercapy-parms.innercapy2,0)}}px;
background-size: {{=Math.max(parms.innercapwidth,parms.innercapwidth2)}}px {{=Math.max(parms.innercapheight,parms.innercapheight2)}}px;"
class="keytop"></div>
<div style="left: {{=parms.innercapx + sizes.strokeWidth}}px; top: {{=parms.innercapy + sizes.strokeWidth}}px;
width: {{=parms.innercapwidth - sizes.strokeWidth*2}}px; height: {{=parms.innercapheight - sizes.strokeWidth*2}}px;
border-radius: {{=sizes.roundInner}}px;
background-color: {{=parms.lightColor}};
background-position: {{=Math.min(parms.innercapx2-parms.innercapx,0)}}px {{=Math.min(parms.innercapy2-parms.innercapy,0)}}px;
background-size: {{=Math.max(parms.innercapwidth,parms.innercapwidth2)}}px {{=Math.max(parms.innercapheight,parms.innercapheight2)}}px;"
class="keytop"></div>
{{?}}
{{?}} {{ /*decal*/ }}
<div style='left: {{=parms.innercapx}}px; top: {{=parms.innercapy}}px; width: {{=parms.innercapwidth}}px; height: {{=parms.innercapheight}}px; padding: {{=sizes.padding}}px;' class='keylabels'>
{{~key.labels :label:i}}
{{? label && label != "" }}
{{ var sanitizedLabel = ""; try { sanitizedLabel = $sanitize(label.replace(/<([^a-zA-Z\/]|$)/,"&lt;$1")); } catch(e) {console.log(e);} }}
{{? sanitizedLabel !== "" }}
{{ var textSize = key.textSize[i] || key.default.textSize; }}
{{ var textColor = key.textColor[i] || key.default.textColor; }}
{{ if(i<9) textColor = lightenColor($color.hex(textColor), 1.2).hex(); }}
<div class='keylabel keylabel{{=i}} textsize{{=textSize}}' style='color:{{=textColor}}; width:{{=parms.textcapwidth}}px; height:{{=parms.textcapheight}}px;'>
<div style='width:{{=parms.textcapwidth}}px; max-width:{{=parms.textcapwidth}}px; height:{{=parms.textcapheight}}px;'>{{=sanitizedLabel}}</div>
</div>
{{??}}
<div class="hint--top hint--rounded" data-hint="Error: Invalid HTML in legend field."><i class="fa fa-times-circle"></i></div>
{{?}}
{{?}}
{{~}}
</div>
{{?}} {{ /*ghost*/ }}
</div>
</script>
<!-- Keycap Template for SVG-->
<script type="text/ng-template" id="keycap_svg">
<g class='{{=key.ghost ? "ghosted" : ""}} {{=key.decal ? "decal" : ""}} keycap'
{{? key.rotation_angle }}
transform="rotate({{=key.rotation_angle}} {{=parms.origin_x}} {{=parms.origin_y}})"
{{?}}>
{{? !key.decal }}
<!-- Outer Border -->
<rect x="{{=parms.outercapx+sizes.strokeWidth}}" y="{{=parms.outercapy+sizes.strokeWidth}}"
width="{{=parms.outercapwidth-sizes.strokeWidth*2}}" height="{{=parms.outercapheight-sizes.strokeWidth*2}}"
rx="{{=sizes.roundOuter}}" fill="{{=parms.darkColor}}" class="outer border"/>
{{? parms.jShaped }}
<rect x="{{=parms.outercapx2+sizes.strokeWidth}}" y="{{=parms.outercapy2+sizes.strokeWidth}}"
width="{{=parms.outercapwidth2-sizes.strokeWidth*2}}" height="{{=parms.outercapheight2-sizes.strokeWidth*2}}"
rx="{{=sizes.roundOuter}}" fill="{{=parms.darkColor}}" class="outer border"/>
{{?}}
<!-- Outer Fill -->
<rect x="{{=parms.outercapx+sizes.strokeWidth}}" y="{{=parms.outercapy+sizes.strokeWidth}}"
width="{{=parms.outercapwidth-sizes.strokeWidth*2}}" height="{{=parms.outercapheight-sizes.strokeWidth*2}}"
rx="{{=sizes.roundOuter}}" fill="{{=parms.darkColor}}"/>
{{? parms.jShaped }}
<rect x="{{=parms.outercapx2+sizes.strokeWidth}}" y="{{=parms.outercapy2+sizes.strokeWidth}}"
width="{{=parms.outercapwidth2-sizes.strokeWidth*2}}" height="{{=parms.outercapheight2-sizes.strokeWidth*2}}"
rx="{{=sizes.roundOuter}}" fill="{{=parms.darkColor}}"/>
{{?}}
{{? !key.ghost }}
<!-- Inner Border -->
<rect x="{{=parms.innercapx+sizes.strokeWidth}}" y="{{=parms.innercapy+sizes.strokeWidth}}"
width="{{=parms.innercapwidth-sizes.strokeWidth*2}}" height="{{=parms.innercapheight-sizes.strokeWidth*2}}"
rx="{{=sizes.roundOuter}}" fill="{{=parms.lightColor}}" class="inner border"/>
{{? parms.jShaped && !key.stepped }}
<rect x="{{=parms.innercapx2+sizes.strokeWidth}}" y="{{=parms.innercapy2+sizes.strokeWidth}}"
width="{{=parms.innercapwidth2-sizes.strokeWidth*2}}" height="{{=parms.innercapheight2-sizes.strokeWidth*2}}"
rx="{{=sizes.roundOuter}}" fill="{{=parms.lightColor}}" class="inner border"/>
{{?}}
<!-- Inner Fill -->
<rect x="{{=parms.innercapx+sizes.strokeWidth}}" y="{{=parms.innercapy+sizes.strokeWidth}}"
width="{{=parms.innercapwidth-sizes.strokeWidth*2}}" height="{{=parms.innercapheight-sizes.strokeWidth*2}}"
rx="{{=sizes.roundOuter}}" fill="{{=parms.lightColor}}"/>
{{? parms.jShaped && !key.stepped }}
<rect x="{{=parms.innercapx2+sizes.strokeWidth}}" y="{{=parms.innercapy2+sizes.strokeWidth}}"
width="{{=parms.innercapwidth2-sizes.strokeWidth*2}}" height="{{=parms.innercapheight2-sizes.strokeWidth*2}}"
rx="{{=sizes.roundOuter}}" fill="{{=parms.lightColor}}"/>
{{?}}
{{? sizes.profile !== "" }}
{{var theProfile = sizes.profile; if(/\b(SPACE)\b/.exec(key.profile)) theProfile = "SPACE";}}
{{? parms.jShaped && !key.stepped }}
{{
/*much harder to do j-shaped keys in SVG*/
theProfile = '_fill'+parms.index;
var rect = {
left: Math.min(parms.innercapx, parms.innercapx2),
top: Math.min(parms.innercapy, parms.innercapy2),
right: Math.min(parms.innercapx+parms.innercapwidth, parms.innercapx2+parms.innercapwidth2),
bottom: Math.min(parms.innercapy+parms.innercapheight, parms.innercapy2+parms.innercapheight2)
};
}}
{{? sizes.profile==="SA" || sizes.profile==="DSA" }}
<radialGradient id="{{=theProfile}}" xlink:href="#{{=sizes.profile}}" gradientUnits="userSpaceOnUse"
cx="{{=(rect.left+rect.right)/2}}" cy="{{=(rect.top+rect.bottom)/2}}"
r="{{=Math.min(rect.right-rect.left, rect.bottom-rect.top)}}"/>
{{??}}
<linearGradient id="{{=theProfile}}" xlink:href="#{{=sizes.profile}}" gradientUnits="userSpaceOnUse"
x1="{{=rect.left}}" y1="{{=rect.top}}" x2="{{=rect.right}}" y2="{{=rect.top}}"/>
{{?}}
<rect x="{{=parms.innercapx2+sizes.strokeWidth}}" y="{{=parms.innercapy2+sizes.strokeWidth}}"
width="{{=parms.innercapwidth2-sizes.strokeWidth*2}}" height="{{=parms.innercapheight2-sizes.strokeWidth*2}}"
rx="{{=sizes.roundOuter}}" fill="{{=parms.lightColor}}"/>
<rect x="{{=parms.innercapx2+sizes.strokeWidth}}" y="{{=parms.innercapy2+sizes.strokeWidth}}"
width="{{=parms.innercapwidth2-sizes.strokeWidth*2}}" height="{{=parms.innercapheight2-sizes.strokeWidth*2}}"
rx="{{=sizes.roundOuter}}" fill="url(#{{=theProfile}})"/>
<rect x="{{=parms.innercapx+sizes.strokeWidth}}" y="{{=parms.innercapy+sizes.strokeWidth}}"
width="{{=parms.innercapwidth-sizes.strokeWidth*2}}" height="{{=parms.innercapheight-sizes.strokeWidth*2}}"
rx="{{=sizes.roundOuter}}" fill="{{=parms.lightColor}}"/>
{{?}}
<rect x="{{=parms.innercapx+sizes.strokeWidth}}" y="{{=parms.innercapy+sizes.strokeWidth}}"
width="{{=parms.innercapwidth-sizes.strokeWidth*2}}" height="{{=parms.innercapheight-sizes.strokeWidth*2}}"
rx="{{=sizes.roundOuter}}" fill="url(#{{=theProfile}})"/>
{{?}}
{{?}} {{ /*ghost*/ }}
{{?}} {{ /*decal*/ }}
</g>
</script>
<script type="text/ng-template" id="keyboard_svg">
<svg width='{{=parms.width+parms.margin*2+parms.padding*2}}{{=parms.units}}'
height='{{=parms.height+parms.margin*2+parms.padding*2}}{{=parms.units}}'
viewBox='0 0 {{=parms.width+parms.margin*2+parms.padding*2}} {{=parms.height+parms.margin*2+parms.padding*2}}'
xmlns='http://www.w3.org/2000/svg'
xmlns:xlink="http://www.w3.org/1999/xlink">
<style type='text/css'>
.keycap .border { stroke: black; stroke-width: {{=parms.strokeWidth*2}}; }
.keycap .inner.border { stroke: rgba(0,0,0,.1); }
</style>
<defs>
<linearGradient id="DCS">
<stop offset="0%" stop-color="black" stop-opacity="0"/>
<stop offset="40%" stop-color="black" stop-opacity="0.1"/>
<stop offset="60%" stop-color="black" stop-opacity="0.1"/>
<stop offset="100%" stop-color="black" stop-opacity="0"/>
</linearGradient>
<linearGradient id="SPACE" x1="0%" x2="0%" y1="0%" y2="100%">
<stop offset="0%" stop-color="black" stop-opacity="0.1"/>
<stop offset="20%" stop-color="black" stop-opacity="0.0"/>
<stop offset="40%" stop-color="black" stop-opacity="0.0"/>
<stop offset="100%" stop-color="black" stop-opacity="0.1"/>
</linearGradient>
<radialGradient id="DSA">
<stop offset="0%" stop-color="black" stop-opacity="0.1"/>
<stop offset="10%" stop-color="black" stop-opacity="0.1"/>
<stop offset="100%" stop-color="black" stop-opacity="0"/>
</radialGradient>
<radialGradient id="SA" xlink:href="#DSA" />
</defs>
<g transform='translate({{=parms.margin}},{{=parms.margin}})'>
<rect width="{{=parms.width+parms.padding*2}}" height="{{=parms.height+parms.padding*2}}"
stroke="#ddd" stroke-width="1" fill="{{=parms.backcolor}}" rx="6"/>
<g transform='translate({{=parms.padding}},{{=parms.padding}})'>
{{=parms.keys}}
</g>
</g>
</svg>
</script>
</div>
<div id='summary_print'></div><!-- just a div to make the summary print work -->
</body>
</html>