feat(device model component): add device model component to create model file

This commit is contained in:
Eric Chen 2019-08-23 14:37:49 +08:00
Родитель 124e0b527f
Коммит 8e6f6e0ec2
50 изменённых файлов: 15564 добавлений и 162 удалений

4
.gitignore поставляемый
Просмотреть файл

@ -1,4 +1,4 @@
out
node_modules
dist
.vscode-test
.vscode-test
cache

Просмотреть файл

@ -0,0 +1,43 @@
const callbackStack = [];
const vscode = acquireVsCodeApi();
function command(cmd, callback) {
if (!cmd) {
return;
}
let args = Array.from(arguments);
if (typeof args[args.length - 1] === 'function') {
callback = args[args.length - 1];
args.length = args.length - 1;
} else {
callback = undefined;
}
args.shift();
const messageId = new Date().getTime() + Math.random();
callbackStack.push({
messageId,
callback
});
vscode.postMessage({
messageId,
command: cmd,
parameter: args
});
}
window.addEventListener('message', event => {
const message = event.data;
for (let index = 0; index < callbackStack.length; index++) {
const callbackItem = callbackStack[index];
if (callbackItem.messageId === message.messageId) {
if (callbackItem.callback) {
callbackItem.callback(message.payload);
}
callbackStack.splice(index, 1);
break;
}
}
});

Просмотреть файл

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 12 12" style="enable-background:new 0 0 12 12;" xml:space="preserve">
<style type="text/css">
.st0{fill:#333333;}
</style>
<title>96dpiClear</title>
<desc>Created with Sketch.</desc>
<g>
<path class="st0" d="M0.1,3.9l0.7-0.7L6,8.4l5.1-5.1l0.7,0.7L6,9.8L0.1,3.9z"/>
</g>
</svg>

После

Ширина:  |  Высота:  |  Размер: 541 B

Просмотреть файл

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 18 18" style="enable-background:new 0 0 18 18;" xml:space="preserve">
<style type="text/css">
.st0{fill:#C8C8C8;}
</style>
<title>96dpiSuccess info</title>
<desc>Created with Sketch.</desc>
<g>
<path class="st0" d="M16.9,1.1v15.8H1.1V1.1H16.9 M18,0H0v18h18V0L18,0z"/>
</g>
</svg>

После

Ширина:  |  Высота:  |  Размер: 544 B

Просмотреть файл

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 18 18" style="enable-background:new 0 0 18 18;" xml:space="preserve">
<style type="text/css">
.st0{fill:#0078D7;}
.st1{enable-background:new ;}
.st2{fill:#FFFFFF;}
</style>
<polyline class="st0" points="16.9,1.1 16.9,16.9 1.1,16.9 1.1,1.1 16.9,1.1 "/>
<title>96dpiSuccess info</title>
<desc>Created with Sketch.</desc>
<g id="Success-info">
<g class="st1">
<path class="st2" d="M13.1,5.2L13.8,6l-6.6,6.6L4,9.4l0.8-0.8L7.2,11L13.1,5.2z"/>
</g>
</g>
<g>
<path class="st2" d="M17,1v16H1V1H17 M18,0H0v18h18V0L18,0z"/>
</g>
</svg>

После

Ширина:  |  Высота:  |  Размер: 799 B

Просмотреть файл

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 18 18" style="enable-background:new 0 0 18 18;" xml:space="preserve">
<style type="text/css">
.st0{enable-background:new ;}
.st1{fill:#C8C8C8;}
</style>
<title>96dpiSuccess info</title>
<desc>Created with Sketch.</desc>
<g id="Success-info">
<g class="st0">
<path class="st1" d="M13.1,5.2L13.8,6l-6.6,6.6L4,9.4l0.8-0.8L7.2,11L13.1,5.2z"/>
</g>
</g>
<g>
<path class="st1" d="M16.9,1.1v15.8H1.1V1.1H16.9 M18,0H0v18h18V0L18,0z"/>
</g>
</svg>

После

Ширина:  |  Высота:  |  Размер: 711 B

Просмотреть файл

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 12 12" style="enable-background:new 0 0 12 12;" xml:space="preserve">
<style type="text/css">
.st0{enable-background:new ;}
.st1{fill:#333333;}
</style>
<title>96dpiClear</title>
<desc>Created with Sketch.</desc>
<g id="Clear">
<g class="st0">
<path class="st1" d="M6.5,6l5.4,5.4l-0.5,0.5L6,6.5l-5.4,5.4l-0.5-0.5L5.5,6L0.1,0.6l0.5-0.5L6,5.5l5.4-5.4l0.5,0.5L6.5,6z"/>
</g>
</g>
</svg>

После

Ширина:  |  Высота:  |  Размер: 655 B

Просмотреть файл

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<g fill="#333">
<path d="M10.711 3L15 7.289V16H5v-3H1V0h5.711l3 3h1zM2 12h3V3h3.29l-2-2H2v11zm4 3h8V8h-4V4H6v11zm5-10.289V7h2.289L11 4.711z"></path>
</g>
</svg>

После

Ширина:  |  Высота:  |  Размер: 268 B

Просмотреть файл

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
<style type="text/css">
.st0{enable-background:new ;}
.st1{fill:#333333;}
</style>
<title>96dpicreate</title>
<desc>Created with Sketch.</desc>
<g id="create">
<g class="st0">
<path class="st1" d="M16,7.5v1H8.5V16h-1V8.5H0v-1h7.5V0h1v7.5H16z"/>
</g>
</g>
</svg>

После

Ширина:  |  Высота:  |  Размер: 603 B

Просмотреть файл

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
<style type="text/css">
.st0{fill:#333333;}
</style>
<title>96dpiDelete</title>
<desc>Created with Sketch.</desc>
<g>
<rect x="6" y="5" class="st0" width="1" height="8"/>
<path class="st0" d="M11,2V1c0-0.6-0.4-1-1-1H7C6.4,0,6,0.4,6,1v1H2v1h1v11v0.5v0.1C3,15.4,3.6,16,4.4,16h0.1H5h7h0.5h0.1
c0.8,0,1.4-0.6,1.4-1.4v-0.1V14V3h1V2H11z M10,1v1H7V1H10z M4.5,15c-0.1,0-0.3,0-0.4-0.1C4,14.8,4,14.6,4,14.5V3h9v11.5
c0,0.1,0,0.3-0.1,0.4C12.8,15,12.6,15,12.5,15H4.5z"/>
<rect x="8" y="5" class="st0" width="1" height="8"/>
<rect x="10" y="5" class="st0" width="1" height="8"/>
</g>
</svg>

После

Ширина:  |  Высота:  |  Размер: 919 B

Просмотреть файл

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
<style type="text/css">
.st0{enable-background:new ;}
.st1{fill:#333333;}
</style>
<title>96dpiDownload</title>
<desc>Created with Sketch.</desc>
<g id="Download">
<g class="st0">
<path class="st1" d="M13.4,8.4l-4.9,4.9L3.6,8.4l0.7-0.7L8,11.3V0h1v11.3l3.6-3.7L13.4,8.4z M4,16v-1h9v1H4z"/>
</g>
</g>
</svg>

После

Ширина:  |  Высота:  |  Размер: 647 B

Просмотреть файл

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
<style type="text/css">
.st0{fill:#A1260D;}
</style>
<title>96dpiError info</title>
<desc>Created with Sketch.</desc>
<path class="st0" d="M11.5,5.2L8.7,8l2.8,2.8l-0.7,0.7L8,8.7l-2.8,2.8l-0.7-0.7L7.3,8L4.5,5.2l0.7-0.7L8,7.3l2.8-2.8L11.5,5.2z M8,1
c3.9,0,7,3.1,7,7s-3.1,7-7,7s-7-3.1-7-7S4.1,1,8,1 M8,0C3.6,0,0,3.6,0,8s3.6,8,8,8s8-3.6,8-8S12.4,0,8,0L8,0z"/>
</svg>

После

Ширина:  |  Высота:  |  Размер: 697 B

Просмотреть файл

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
<style type="text/css">
.st0{enable-background:new ;}
.st1{fill:#333333;}
</style>
<title>96dpiFilter</title>
<desc>Created with Sketch.</desc>
<g id="Filter">
<g class="st0">
<path class="st1" d="M0,1h16v1.7l-6,6V15H6V8.7l-6-6V1z M15,2.3V2H1v0.3l6,6V14h2V8.3L15,2.3z"/>
</g>
</g>
</svg>

После

Ширина:  |  Высота:  |  Размер: 629 B

Просмотреть файл

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
<style type="text/css">
.st0{fill:#00529C;}
</style>
<path class="st0" d="M7.5,1C11.1,1,14,3.9,14,7.5S11.1,14,7.5,14S1,11.1,1,7.5S3.9,1,7.5,1 M7.5,0C3.4,0,0,3.4,0,7.5S3.4,15,7.5,15
S15,11.6,15,7.5S11.6,0,7.5,0L7.5,0z"/>
<title>96dpiInfo</title>
<desc>Created with Sketch.</desc>
<rect x="7" y="4" class="st0" width="1" height="1"/>
<rect x="7" y="6" class="st0" width="1" height="5"/>
</svg>

После

Ширина:  |  Высота:  |  Размер: 726 B

Просмотреть файл

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16 4" style="enable-background:new 0 0 16 4;" xml:space="preserve">
<style type="text/css">
.st0{opacity:0.3;fill:#333333;}
</style>
<title>96dpiShow more</title>
<desc>Created with Sketch.</desc>
<circle class="st0" cx="2" cy="2" r="1"/>
<circle class="st0" cx="8" cy="2" r="1"/>
<circle class="st0" cx="14" cy="2" r="1"/>
</svg>

После

Ширина:  |  Высота:  |  Размер: 594 B

Просмотреть файл

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
<style type="text/css">
.st0{enable-background:new ;}
.st1{fill:#333333;}
</style>
<title>96dpiPublish</title>
<desc>Created with Sketch.</desc>
<g id="Publish">
<g class="st0">
<path class="st1" d="M15,4.3V16H2V0h8.7L15,4.3z M14,15V5h-4V1H3v14H14z M11,4h2.3L11,1.7V4z"/>
</g>
<g>
<path class="st1" d="M12,9.5c-0.8,0-1.5,0.7-1.5,1.5v1.5c0,0.3-0.2,0.5-0.5,0.5H9.5v1H10c0.8,0,1.5-0.7,1.5-1.5V11
c0-0.3,0.2-0.5,0.5-0.5h0.5v-1H12z"/>
<path class="st1" d="M10,6c0.8,0,1.5,0.7,1.5,1.5V9c0,0.3,0.2,0.5,0.5,0.5h0.5v1H12c-0.8,0-1.5-0.7-1.5-1.5V7.5
C10.5,7.2,10.3,7,10,7H9.5V6H10z"/>
<path class="st1" d="M5,9.5c0.8,0,1.5,0.7,1.5,1.5v1.5C6.5,12.8,6.7,13,7,13h0.5v1H7c-0.8,0-1.5-0.7-1.5-1.5V11
c0-0.3-0.2-0.5-0.5-0.5H4.5v-1H5z"/>
<path class="st1" d="M7,6C6.2,6,5.5,6.7,5.5,7.5V9c0,0.3-0.2,0.5-0.5,0.5H4.5v1H5c0.8,0,1.5-0.7,1.5-1.5V7.5C6.5,7.2,6.7,7,7,7
h0.5V6H7z"/>
</g>
</g>
</svg>

После

Ширина:  |  Высота:  |  Размер: 1.2 KiB

Просмотреть файл

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 18 18" style="enable-background:new 0 0 18 18;" xml:space="preserve">
<style type="text/css">
.st0{fill:#C8C8C8;}
</style>
<title>96dpiSuccess info</title>
<desc>Created with Sketch.</desc>
<path class="st0" d="M9,0C4,0,0,4,0,9s4,9,9,9s9-4,9-9S14,0,9,0z M9,16.9c-4.3,0-7.9-3.5-7.9-7.9S4.7,1.1,9,1.1s7.9,3.5,7.9,7.9
S13.3,16.9,9,16.9z"/>
</svg>

После

Ширина:  |  Высота:  |  Размер: 608 B

Просмотреть файл

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 18 18" style="enable-background:new 0 0 18 18;" xml:space="preserve">
<style type="text/css">
.st0{fill:#0078D7;}
</style>
<title>96dpiSuccess info</title>
<desc>Created with Sketch.</desc>
<path class="st0" d="M9,0C4,0,0,4,0,9s4,9,9,9s9-4,9-9S14,0,9,0z M9,16.9c-4.3,0-7.9-3.5-7.9-7.9S4.7,1.1,9,1.1s7.9,3.5,7.9,7.9
S13.3,16.9,9,16.9z"/>
<circle class="st0" cx="9" cy="9" r="5.9"/>
</svg>

После

Ширина:  |  Высота:  |  Размер: 652 B

Просмотреть файл

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 18 18" style="enable-background:new 0 0 18 18;" xml:space="preserve">
<style type="text/css">
.st0{fill:#C8C8C8;}
</style>
<title>96dpiSuccess info</title>
<desc>Created with Sketch.</desc>
<path class="st0" d="M9,0C4,0,0,4,0,9s4,9,9,9s9-4,9-9S14,0,9,0z M9,16.9c-4.3,0-7.9-3.5-7.9-7.9S4.7,1.1,9,1.1s7.9,3.5,7.9,7.9
S13.3,16.9,9,16.9z"/>
<circle class="st0" cx="9" cy="9" r="5.9"/>
</svg>

После

Ширина:  |  Высота:  |  Размер: 652 B

Просмотреть файл

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
<style type="text/css">
.st0{fill:#333333;}
</style>
<title>96dpiRefresh</title>
<desc>Created with Sketch.</desc>
<path class="st0" d="M10.1,0.3l-0.3,1C12.8,2,15,4.8,15,8c0,3.9-3.1,7-7,7s-7-3.1-7-7c0-2.8,1.6-5.2,4-6.3l0,0V4h1V0H2v1h2.1l0,0
C1.7,2.4,0,5,0,8c0,4.4,3.6,8,8,8s8-3.6,8-8C16,4.3,13.5,1.2,10.1,0.3z"/>
</svg>

После

Ширина:  |  Высота:  |  Размер: 654 B

Просмотреть файл

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
<style type="text/css">
.st0{fill:#333333;}
</style>
<title>96dpiSearch</title>
<desc>Created with Sketch.</desc>
<path class="st0" d="M10.5,0C7.5,0,5,2.5,5,5.5C5,6.8,5.5,8.1,6.3,9l-6.1,6.1c-0.2,0.2-0.2,0.5,0,0.7C0.2,16,0.4,16,0.5,16
s0.3,0,0.4-0.1L7,9.7c1,0.8,2.2,1.3,3.5,1.3c3,0,5.5-2.5,5.5-5.5S13.5,0,10.5,0z M10.5,10C8,10,6,8,6,5.5S8,1,10.5,1S15,3,15,5.5
S13,10,10.5,10z"/>
</svg>

После

Ширина:  |  Высота:  |  Размер: 720 B

Просмотреть файл

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
<style type="text/css">
.st0{enable-background:new ;}
.st1{fill:#388A34;}
</style>
<title>96dpiSuccess info</title>
<desc>Created with Sketch.</desc>
<g id="Success-info">
<g class="st0">
<path class="st1" d="M11.6,4.6l0.7,0.7l-5.9,5.9L3.6,8.4l0.7-0.7l2.1,2.1L11.6,4.6z"/>
</g>
</g>
<g>
<path class="st1" d="M8,1c3.9,0,7,3.1,7,7s-3.1,7-7,7s-7-3.1-7-7S4.1,1,8,1 M8,0C3.6,0,0,3.6,0,8s3.6,8,8,8s8-3.6,8-8S12.4,0,8,0
L8,0z"/>
</g>
</svg>

После

Ширина:  |  Высота:  |  Размер: 778 B

Просмотреть файл

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
<style type="text/css">
.st0{enable-background:new ;}
.st1{fill:#D73B02;}
</style>
<title>96dpiWarning info</title>
<desc>Created with Sketch.</desc>
<g id="Warning-info">
<g class="st0">
<path class="st1" d="M7.5,0L15,15H0L7.5,0z M7.5,2.2L1.6,14h11.8L7.5,2.2z M7,6h1v5H7V6z M7,13v-1h1v1H7z"/>
</g>
</g>
</svg>

После

Ширина:  |  Высота:  |  Размер: 652 B

Просмотреть файл

@ -0,0 +1,178 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="style.css">
<script src="vue.js"></script>
</head>
<body>
<div id="main" v-bind:class="{'show-search-bar': showSearchBar, 'show-search-result': searchKeywords !== ''}" v-cloak>
<header v-bind:class="{'show-status-selector': showStatusSelector, 'show-tag-selector': showTagSelector}">
<h1 class="company-name">{{ companyName }}</h1>
<div class="digital-twin-file-type-tab">
<span v-bind:class="{active: type.value === 'CapabilityModel'}" v-on:click="type.value = 'CapabilityModel'">Device Capability Models</span>
<span v-bind:class="{active: type.value === 'Interface'}" v-on:click="type.value = 'Interface'">Interfaces</span>
</div>
<div class="action-bar">
<button class="with-icon new" v-if="!publicRepository" v-on:click="createDigitalTwinFile">New</button>
<button class="with-icon download" v-on:click="editDigitalTwinFiles" v-bind:disabled="type.value === 'Interface' && !this.selectedInterfaces.value.length || type.value === 'CapabilityModel' && !this.selectedCapabilityModels.value.length">Download</button>
<button class="with-icon publish" v-if="false" v-on:click="publishDigitalTwinFiles" v-bind:disabled="hasNoItemToPublish()">Publish</button>
<button class="with-icon delete" v-if="!publicRepository" v-on:click="deleteDigitalTwinFiles" v-bind:disabled="type.value === 'Interface' && !this.selectedInterfaces.value.length || type.value === 'CapabilityModel' && !this.selectedCapabilityModels.value.length">Delete</button>
<button class="with-icon refresh right" v-on:click="refreshDigitalTwinFileList"></button>
<button class="with-icon filter right" v-on:click="showHideSearchBar"></button>
</div>
<div class="with-icon search-bar">
<input type="text" placeholder="Filter by keyword" v-model="filterKeywords">
<button class="with-icon clear right" v-on:click="clearFilter"></button>
<button class="dropdown tags right" v-on:click="showHideTagSelector">{{ filterTags.length ? (filterTags.length > 1 ? (filterTags.length + ' Tags') : '1 Tag') : 'Tags' }}</button>
<button class="dropdown status right" v-on:click="showHideStatusSelector">{{ filterStatus === 'All' ? 'Status' : filterStatus }}</button>
</div>
<div class="tag-selector">
<div class="tag-list">
<div class="tag-list-item" v-for="tag in searchTags(allTags.value)">
<label><input type="checkbox" v-bind:value="tag" v-model="filterTags">{{ tag }}</label>
</div>
</div>
</div>
<div class="tag-selector-search-bar">
<div class="with-icon tag-search-bar">
<input type="text" placeholder="Search" v-model="filterTagsKeywords">
</div>
<div class="tag-search-action">
<div class="tag-and-or">
<input type="radio" name="tag-and-or" value="or" id="tag-or" v-model="filterTagsOrAnd"><label for="tag-or">or</label>
<input type="radio" name="tag-and-or" value="and" id="tag-and" v-model="filterTagsOrAnd"><label for="tag-and">and</label>
</div>
<div class="tag-clear" v-on:click="filterTags = []" v-bind:class="{disabled: !filterTags.length}">Clear</div>
</div>
</div>
<div class="status-selector">
<div class="status-item" v-on:click="selectFilterStatus('All')">All</div>
<div class="status-item" v-on:click="selectFilterStatus('Saved')">Saved</div>
<div class="status-item" v-on:click="selectFilterStatus('Published')">Published</div>
</div>
</header>
<div class="content">
<div class="search_result" v-if="searchKeywords !== ''">Search results with keywords: <strong>{{searchKeywords}}</strong>. Click <span class="link" v-on:click="clearKeywords">here</span> to clear keywords.</div>
<table v-show="type.value === 'Interface'">
<thead>
<tr>
<th></th>
<th>Name</th>
<th>ID</th>
<th>Version</th>
<th>Publisher</th>
</tr>
</thead>
<tbody v-on:wheel="onScrollTable" v-on:touchmove="onScrollTable" id="interfaceListTable">
<tr v-show="filterKeywords && filterKeywords !== searchKeywords" v-on:click="searchOnServer">
<td colspan="5" style="width: 100%"><span class="info_icon"></span>Results below are filtered from local cache. Click here to search on server.</td>
</tr>
<tr v-for="interface in filterItems(interfaceList.value)" v-on:click="addRemoveInterface(interface.urnId)">
<td><input type="checkbox" v-bind:value="interface.urnId" v-model="selectedInterfaces.value"></td>
<td v-bind:title="interface.displayName"><div class="td_inner" v-html="highlight(interface.displayName)"></div></td>
<td v-bind:title="interface.urnId"><div class="td_inner" v-html="highlight(interface.urnId)"></div><span class="copy_icon" title="Copy" v-on:click.stop="copy($event, interface.urnId)"></span></td>
<td v-bind:title="interface.version"><div class="td_inner">{{ interface.version }}</div></td>
<td>{{ interface.publisherName }}</td>
</tr>
<tr class="loading-digital-twin-files" v-show="loadingDigitalTwinInterfaces.value">
<td></td><td></td><td></td><td></td><td></td>
</tr>
<tr class="loading-digital-twin-files" v-show="loadingDigitalTwinInterfaces.value">
<td></td><td></td><td></td><td></td><td></td>
</tr>
<tr class="loading-digital-twin-files" v-show="loadingDigitalTwinInterfaces.value">
<td></td><td></td><td></td><td></td><td></td>
</tr>
<tr class="loading-digital-twin-files" v-show="loadingDigitalTwinInterfaces.value">
<td></td><td></td><td></td><td></td><td></td>
</tr>
<tr class="loading-digital-twin-files" v-show="loadingDigitalTwinInterfaces.value">
<td></td><td></td><td></td><td></td><td></td>
</tr>
<tr class="loading-digital-twin-files" v-show="loadingDigitalTwinInterfaces.value">
<td></td><td></td><td></td><td></td><td></td>
</tr>
<tr class="loading-digital-twin-files" v-show="loadingDigitalTwinInterfaces.value">
<td></td><td></td><td></td><td></td><td></td>
</tr>
<tr class="loading-digital-twin-files" v-show="loadingDigitalTwinInterfaces.value">
<td></td><td></td><td></td><td></td><td></td>
</tr>
<tr class="loading-digital-twin-files" v-show="loadingDigitalTwinInterfaces.value">
<td></td><td></td><td></td><td></td><td></td>
</tr>
<tr class="loading-digital-twin-files" v-show="loadingDigitalTwinInterfaces.value">
<td></td><td></td><td></td><td></td><td></td>
</tr>
<tr v-show="!loadingDigitalTwinInterfaces.value && interfaceNextToken.value">
<td colspan="5"></td>
</tr>
</tbody>
</table>
<table v-show="type.value === 'CapabilityModel'">
<thead>
<tr>
<th></th>
<th>Name</th>
<th>ID</th>
<th>Version</th>
<th>Publisher</th>
</tr>
</thead>
<tbody v-on:wheel="onScrollTable" v-on:touchmove="onScrollTable" id="capabilityListTable">
<tr v-show="filterKeywords && filterKeywords !== searchKeywords" v-on:click="searchOnServer">
<td colspan="5" style="width: 100%"><span class="info_icon"></span>Results below are filtered from local cache. Click here to search on server.</td>
</tr>
<tr v-for="capability in filterItems(capabilityList.value)" v-on:click="addRemoveCapability(capability.urnId)">
<td><input type="checkbox" v-bind:value="capability.urnId" v-model="selectedCapabilityModels.value"></td>
<td v-bind:title="capability.displayName"><div class="td_inner" v-html="highlight(capability.displayName)"></div></td>
<td v-bind:title="capability.urnId"><div class="td_inner" v-html="highlight(capability.urnId)"></div><span class="copy_icon" title="Copy" v-on:click.stop="copy($event, capability.urnId)"></span></td>
<td v-bind:title="capability.version"><div class="td_inner">{{ capability.version }}</div></td>
<td>{{ capability.publisherName }}</td>
</tr>
<tr class="loading-digital-twin-files" v-show="loadingDigitalTwinCapabilityModels.value">
<td></td><td></td><td></td><td></td><td></td>
</tr>
<tr class="loading-digital-twin-files" v-show="loadingDigitalTwinCapabilityModels.value">
<td></td><td></td><td></td><td></td><td></td>
</tr>
<tr class="loading-digital-twin-files" v-show="loadingDigitalTwinCapabilityModels.value">
<td></td><td></td><td></td><td></td><td></td>
</tr>
<tr class="loading-digital-twin-files" v-show="loadingDigitalTwinCapabilityModels.value">
<td></td><td></td><td></td><td></td><td></td>
</tr>
<tr class="loading-digital-twin-files" v-show="loadingDigitalTwinCapabilityModels.value">
<td></td><td></td><td></td><td></td><td></td>
</tr>
<tr class="loading-digital-twin-files" v-show="loadingDigitalTwinCapabilityModels.value">
<td></td><td></td><td></td><td></td><td></td>
</tr>
<tr class="loading-digital-twin-files" v-show="loadingDigitalTwinCapabilityModels.value">
<td></td><td></td><td></td><td></td><td></td>
</tr>
<tr class="loading-digital-twin-files" v-show="loadingDigitalTwinCapabilityModels.value">
<td></td><td></td><td></td><td></td><td></td>
</tr>
<tr class="loading-digital-twin-files" v-show="loadingDigitalTwinCapabilityModels.value">
<td></td><td></td><td></td><td></td><td></td>
</tr>
<tr class="loading-digital-twin-files" v-show="loadingDigitalTwinCapabilityModels.value">
<td></td><td></td><td></td><td></td><td></td>
</tr>
<tr v-show="!loadingDigitalTwinCapabilityModels.value && capabilityNextToken.value">
<td colspan="5"></td>
</tr>
</tbody>
</table>
</div>
</div>
<script src="command.js"></script>
<script src="main.js"></script>
</body>
</html>

Просмотреть файл

@ -0,0 +1,403 @@
var repository = new Vue({
el: "#main",
data: {
companyName:
_location.search === "?public"
? "Public repository"
: "Company repository",
selectedInterfaces: {
value: []
},
interfaceList: {
value: []
},
selectedCapabilityModels: {
value: []
},
capabilityList: {
value: []
},
type: {
value: "CapabilityModel"
},
interfaceNextToken: {
value: ""
},
capabilityNextToken: {
value: ""
},
showSearchBar: false,
showStatusSelector: false,
showTagSelector: false,
filterStatus: "All",
filterTags: [],
filterKeywords: "",
searchKeywords: "",
filterTagsOrAnd: "and",
loadingDigitalTwinInterfaces: {
value: true
},
loadingDigitalTwinCapabilityModels: {
value: true
},
allTags: {
value: [
"tag1",
"tag2",
"tag3",
"tag11",
"tag12",
"tag13",
"tag21",
"tag22",
"tag23",
"tag31",
"tag32",
"tag33"
]
},
filterTagsKeywords: "",
nextPageLoadingCounter: null,
publicRepository: _location.search === "?public"
},
methods: {
command,
highlight: function(value) {
value = encodeHTML(value);
const filterKeywords = encodeHTML(this.filterKeywords.trim());
if (!filterKeywords) {
return value;
}
const filterReg = new RegExp(`(${filterKeywords})`, "ig");
return value.replace(filterReg, "<em>$1</em>");
},
createDigitalTwinFile,
deleteDigitalTwinFiles,
editDigitalTwinFiles,
getNextPageDigitalTwinFiles,
refreshDigitalTwinFileList,
showHideSearchBar,
showHideStatusSelector,
showHideTagSelector,
clearFilter,
selectFilterStatus,
addRemoveInterface,
addRemoveCapability,
filterItems,
onScrollTable,
searchTags,
copy,
hasNoItemToPublish,
searchOnServer,
clearKeywords
},
created: function() {
getNextPageDigitalTwinFiles.call(this, "Interface");
getNextPageDigitalTwinFiles.call(this, "CapabilityModel");
}
});
function encodeHTML(value) {
let div = document.createElement("div");
div.innerText = value;
const html = div.innerHTML;
div = undefined;
return html;
}
function deleteDigitalTwinFiles() {
const fileIds =
this.type.value === "Interface"
? this.selectedInterfaces.value
: this.selectedCapabilityModels.value;
command(
"iotworkbench.deleteMetamodelFiles",
fileIds,
this.type.value,
refreshDigitalTwinFileList.bind(this)
);
}
function editDigitalTwinFiles() {
const fileIds =
this.type.value === "Interface"
? this.selectedInterfaces.value
: this.selectedCapabilityModels.value;
command(
"iotworkbench.editMetamodelFiles",
fileIds,
this.type.value,
this.publicRepository,
refreshDigitalTwinFileList.bind(this)
);
}
function createDigitalTwinFile() {
const commandName =
this.type.value === "Interface"
? "iotworkbench.iotPnPCreateInterface"
: "iotworkbench.iotPnPCreateCapabilityModel";
command(commandName);
}
function getNextPageDigitalTwinFiles(fileType) {
fileType = typeof fileType === "string" ? fileType : this.type.value;
let commandName, fileList, nextToken, loadingDigitalTwinFiles;
if (fileType === "Interface") {
commandName = "iotworkbench.getInterfaces";
fileList = this.interfaceList;
nextToken = this.interfaceNextToken;
loadingDigitalTwinFiles = this.loadingDigitalTwinInterfaces;
tableId = "interfaceListTable";
} else {
commandName = "iotworkbench.getCapabilityModels";
fileList = this.capabilityList;
nextToken = this.capabilityNextToken;
loadingDigitalTwinFiles = this.loadingDigitalTwinCapabilityModels;
tableId = "capabilityListTable";
}
loadingDigitalTwinFiles.value = true;
command(
commandName,
this.searchKeywords,
this.publicRepository,
50,
nextToken.value,
res => {
Vue.set(fileList, "value", fileList.value.concat(res.result.results));
Vue.set(nextToken, "value", res.result.continuationToken);
Vue.set(loadingDigitalTwinFiles, "value", false);
}
);
}
function refreshDigitalTwinFileList() {
let nextToken, selectedList;
if (this.type.value === "Interface") {
fileList = this.interfaceList;
nextToken = this.interfaceNextToken;
selectedList = this.selectedInterfaces;
loadingDigitalTwinFiles = this.loadingDigitalTwinInterfaces;
} else {
fileList = this.capabilityList;
nextToken = this.capabilityNextToken;
selectedList = this.selectedCapabilityModels;
loadingDigitalTwinFiles = this.loadingDigitalTwinCapabilityModels;
}
Vue.set(fileList, "value", []);
Vue.set(nextToken, "value", "");
Vue.set(selectedList, "value", []);
Vue.set(loadingDigitalTwinFiles, "value", true);
setTimeout(getNextPageDigitalTwinFiles.bind(this), 1000); // wait for server refresh
}
function showHideSearchBar() {
this.showSearchBar = !this.showSearchBar;
this.showStatusSelector = false;
this.showTagSelector = false;
}
function showHideStatusSelector() {
this.showStatusSelector = !this.showStatusSelector;
this.showTagSelector = false;
}
function showHideTagSelector() {
this.showTagSelector = !this.showTagSelector;
this.showStatusSelector = false;
}
function clearFilter() {
this.filterTags = [];
this.filterStatus = "All";
this.showStatusSelector = false;
this.filterKeywords = "";
this.showTagSelector = false;
}
function selectFilterStatus(status) {
this.filterStatus = status;
this.showStatusSelector = false;
}
function addRemoveInterface(id) {
const index = this.selectedInterfaces.value.indexOf(id);
if (index !== -1) {
this.selectedInterfaces.value.splice(index, 1);
} else {
this.selectedInterfaces.value.push(id);
}
}
function addRemoveCapability(id) {
const index = this.selectedCapabilityModels.value.indexOf(id);
if (index !== -1) {
this.selectedCapabilityModels.value.splice(index, 1);
} else {
this.selectedCapabilityModels.value.push(id);
}
}
function searchTags(tags) {
const filterTagsKeywords = this.filterTagsKeywords.trim();
if (!filterTagsKeywords) {
return tags;
}
return tags.filter(tag => {
return tag.toLowerCase().indexOf(filterTagsKeywords.toLowerCase()) !== -1;
});
}
function filterItems(list) {
if (!this.showSearchBar) {
return list;
}
const filterKeywords = this.filterKeywords.trim();
const filterStatus = this.filterStatus;
const filterTags = this.filterTags;
const filterTagsOrAnd = this.filterTagsOrAnd;
return list.filter(item => {
if (filterStatus !== "All") {
if (item.published && filterStatus !== "Published") {
return false;
}
if (!item.published && filterStatus !== "Saved") {
return false;
}
}
if (!isMatchTags(item.tags, filterTags, filterTagsOrAnd)) {
return false;
}
if (!item.displayName) {
return false;
}
const displayName = item.displayName;
const urnId = item.urnId;
if (
filterKeywords &&
displayName.toLowerCase().indexOf(filterKeywords.toLowerCase()) === -1 &&
urnId.toLowerCase().indexOf(filterKeywords.toLowerCase()) === -1
) {
return false;
}
return true;
});
}
function isMatchTags(tags, selectedTags, orAnd) {
if ((!tags || !tags.length) && selectedTags.length) {
return false;
}
if (!selectedTags.length) {
return true;
}
for (tag of selectedTags) {
const index = tags.indexOf(tag);
if (index === -1 && orAnd === "and") {
return false;
}
if (index !== -1 && orAnd === "or") {
return true;
}
if (index !== -1 && orAnd === "and") {
tags.splice(index, 1);
}
}
if (orAnd === "or") {
return false;
}
if (orAnd === "and" && !tags.length) {
return true;
}
return false;
}
function onScrollTable(event) {
if (this.filterKeywords) {
return;
}
const nextToken =
this.type.value === "Interface"
? this.interfaceNextToken.value
: this.capabilityNextToken.value;
const loadingDigitalTwinFiles =
this.type.value === "Interface"
? this.loadingDigitalTwinInterfaces
: this.loadingDigitalTwinCapabilityModels;
if (
!nextToken ||
this.nextPageLoadingCounter ||
loadingDigitalTwinFiles.value
) {
return;
}
this.nextPageLoadingCounter = setTimeout(() => {
const totalHeight = event.target.scrollHeight;
const heightOffset = event.target.scrollTop;
const viewHeight = event.target.offsetHeight;
if (viewHeight + heightOffset >= totalHeight) {
this.getNextPageDigitalTwinFiles(this.type);
}
this.nextPageLoadingCounter = null;
}, 1000);
}
function copy(event, content) {
const copyTextBox = document.createElement("input");
copyTextBox.className = "copy-text-box";
copyTextBox.value = content;
document.body.appendChild(copyTextBox);
copyTextBox.select();
document.execCommand("copy");
document.body.removeChild(copyTextBox);
event.target.className = "copy_icon copied";
setTimeout(() => {
event.target.className = "copy_icon";
}, 500);
}
function hasNoItemToPublish() {
let selectedItemList, fullItemList;
if (this.type.value === "Interface") {
fullItemList = this.interfaceList.value;
selectedItemList = this.selectedInterfaces.value;
} else {
fullItemList = this.capabilityList.value;
selectedItemList = this.selectedCapabilityModels.value;
}
for (let i = 0; i < selectedItemList.length; i++) {
const item = fullItemList.find(item => item.id === selectedItemList[i]);
if (item && !item.published) {
return false;
}
}
return true;
}
function searchOnServer() {
this.searchKeywords = this.filterKeywords;
this.filterKeywords = "";
this.refreshDigitalTwinFileList();
}
function clearKeywords() {
this.searchKeywords = "";
this.filterKeywords = "";
this.refreshDigitalTwinFileList();
}

Просмотреть файл

@ -0,0 +1,895 @@
[v-cloak] {
display: none;
}
html {
height: 100%;
}
body {
height: 100%;
width: 100%;
min-width: 600px;
margin: 0;
padding: 0 20px;
color: #333;
font-family: "Segoe UI", SegoeUI, "Helvetica Neue", Helvetica, Arial, sans-serif;
box-sizing: border-box;
position: absolute;
background: white;
user-select: none;
}
em {
font-weight: bold;
font-style: normal;
background: rgba(0, 120, 215, 0.2);
}
input[type=checkbox] {
width: 20px;
height: 0;
margin: 0;
margin-right: 8px;
padding: 0;
position: relative;
}
input[type=checkbox]::after {
content: "";
background: url(image/Checkbox.svg) no-repeat center;
background-size: 18px 18px;
display: block;
position: absolute;
top: -14px;
width: 20px;
height: 20px;
}
input[type=checkbox]:hover::after {
background-image: url(image/CheckboxHover.svg);
}
input[type=checkbox]:checked::after {
background-image: url(image/CheckboxChecked.svg);
}
input[type=radio] {
width: 20px;
height: 0;
margin: 0;
margin-right: 8px;
padding: 0;
position: relative;
}
input[type=radio]::after {
content: "";
background: url(image/Radio.svg) no-repeat center;
background-size: 18px 18px;
display: block;
position: absolute;
top: -14px;
width: 20px;
height: 20px;
}
input[type=radio]:hover::after {
background-image: url(image/RadioHover.svg);
}
input[type=radio]:checked::after {
background-image: url(image/RadioChecked.svg);
}
.right {
float: right;
}
button {
outline: none;
}
button:not(:disabled) {
cursor: pointer;
}
button:not(:disabled):hover {
background: rgba(0, 120, 215, 0.1);
}
button:not(:disabled):active {
background: rgba(0, 120, 215, 0.2)!important;
}
button:disabled {
color: rgba(102, 102, 102, 0.3);
cursor: default!important;
}
button:disabled::before {
background-color: rgba(102, 102, 102, 0.3);
}
.with-icon::before {
content: "";
display: inline-block;
vertical-align: top;
width: 16px;
height: 16px;
margin-right: 8px;
box-sizing: border-box;
background-color: #333;
-webkit-mask-position: center;
mask-position: center;
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
-webkit-mask-size: contain;
mask-size: contain;
}
#main {
height: 100%;
}
header {
padding-top: 20px;
width: 100%;
}
header h1.company-name {
font-size: 20px;
padding: 0;
margin: 0;
margin-bottom: 20px;
display: block;
line-height: 30px;
height: 30px;
}
header .digital-twin-file-type-tab {
font-size: 15px;
margin-bottom: 20px;
}
header .digital-twin-file-type-tab span {
margin-right: 20px;
height: 25px;
line-height: 25px;
font-weight: bold;
border-bottom: solid 2px transparent;
box-sizing: border-box;
display: inline-block;
cursor: pointer;
}
header .digital-twin-file-type-tab span.active {
border-color: #0078d7;
}
header .action-bar {
margin-bottom: 5px;
}
header .action-bar::after {
content: "";
clear: both;
}
header .action-bar button {
padding: 8px;
border: none;
font-size: 14px;
background: transparent;
height: 32px;
line-height: 16px;
box-sizing: border-box;
}
header .action-bar button.right::before {
margin: 0;
}
header .action-bar button.new::before {
-webkit-mask-image: url(image/Create.svg);
mask-image: url(image/Create.svg);
}
header .action-bar button.download::before {
-webkit-mask-image: url(image/Download.svg);
mask-image: url(image/Download.svg);
}
header .action-bar button.publish::before {
-webkit-mask-image: url(image/Publish.svg);
mask-image: url(image/Publish.svg);
}
header .action-bar button.delete::before {
-webkit-mask-image: url(image/Delete.svg);
mask-image: url(image/Delete.svg);
}
header .action-bar button.filter::before {
-webkit-mask-image: url(image/Filter.svg);
mask-image: url(image/Filter.svg);
}
header .action-bar button.refresh::before {
-webkit-mask-image: url(image/Refresh.svg);
mask-image: url(image/Refresh.svg);
}
#main.show-search-bar header .action-bar button.filter {
background: rgba(0, 120, 215, 0.2);
}
header .search-bar {
border: 1px solid rgba(102, 102, 102, 0.3);
box-sizing: border-box;
height: 32px;
margin: 0 8px 5px 8px;
font-size: 0;
display: none;
}
#main.show-search-bar header .search-bar {
display: block;
}
header .search-bar:focus-within {
border-color: rgba(0, 120, 215, 0.6);
box-shadow: 0 0 1px rgba(0, 120, 215, 0.6);
}
header .search-bar::before {
margin: 7px;
-webkit-mask-image: url(image/Search.svg);
mask-image: url(image/Search.svg);
}
header .search-bar input[type=text] {
border: none;
height: 30px;
width: calc(100% - 65px);
line-height: 30px;
font-size: 12px;
color: #333;
background: transparent;
vertical-align: top;
outline: none;
padding: 0;
}
header .search-bar input[type=text]::placeholder {
color: #333;
opacity: 0.6;
}
header .search-bar::after {
content: "";
clear: both;
}
header .search-bar button {
height: 30px;
background: none;
border: none;
outline: none;
padding: 0;
}
header .search-bar button.clear {
width: 30px;
}
header .search-bar button.clear::before {
margin: 7px;
-webkit-mask-image: url(image/Clear.svg);
mask-image: url(image/Clear.svg);
}
header .search-bar button.dropdown {
width: 80px;
padding: 0 5px;
line-height: 30px;
position: relative;
margin-left: 10px;
text-align: right;
}
header .search-bar button.dropdown::after {
content: "";
float: right;
vertical-align: top;
margin: 9px 0 9px 8px;
width: 12px;
height: 12px;
background-color: #333;
-webkit-mask-position: center;
mask-position: center;
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
-webkit-mask-size: contain;
mask-size: contain;
-webkit-mask-image: url(image/Arrow.svg);
mask-image: url(image/Arrow.svg);
}
header .tag-selector {
position: absolute;
top: 184px;
right: 58px;
border-top: 65px solid white;
box-sizing: border-box;
max-height: calc(100% - 190px);
width: 200px;
background: white;
box-shadow: 0px 0px 5px 0px rgba(102, 102, 102, 0.3);
display: none;
overflow-y: auto;
font-size: 14px;
z-index: 100;
}
header .tag-selector::-webkit-scrollbar {
width: 10px;
background: #eee;
}
header .tag-selector::-webkit-scrollbar-thumb {
background-color: #ccc;
border: 2px solid #eee;
border-radius: 5px;
}
header .tag-selector .tag-list {
position: relative;
}
header .tag-selector .tag-list-item:hover {
background: rgba(0, 120, 215, 0.1);
}
header .tag-selector .tag-list-item:active {
background: rgba(0, 120, 215, 0.2);
}
header .tag-selector .tag-list-item label {
height: 32px;
line-height: 32px;
padding: 0 10px;
width: 100%;
box-sizing: border-box;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: block;
}
header .tag-selector-search-bar {
position: absolute;
top: 184px;
right: 58px;
width: 200px;
z-index: 200;
display: none;
}
header .tag-selector-search-bar .tag-search-bar {
height: 32px;
font-size: 0;
border-bottom: 1px solid rgba(102, 102, 102, 0.1);
}
header .tag-selector-search-bar .tag-search-bar::before {
margin: 0;
position: absolute;
top: 8px;
right: 8px;
-webkit-mask-image: url(image/Search.svg);
mask-image: url(image/Search.svg);
}
header .tag-selector-search-bar .tag-search-bar input[type=text] {
border: none;
background: none;
padding: 0 5px;
height: 32px;
width: calc(100% - 32px);
outline: none;
}
header .tag-selector-search-bar .tag-search-bar input[type=text]::placeholder {
color: #333;
opacity: 0.6;
}
header .tag-selector-search-bar .tag-and-or {
height: 20px;
line-height: 20px;
font-size: 14px;
margin: 6px 0;
display: inline-block;
}
header .tag-selector-search-bar .tag-and-or label {
height: 20px;
line-height: 20px;
vertical-align: top;
}
header .tag-selector-search-bar .tag-clear {
height: 20px;
line-height: 20px;
font-size: 14px;
border-left: 1px solid rgba(102, 102, 102, 0.1);
padding: 0 8px;
margin: 6px 0;
float: right;
color: #0078d7;
cursor: pointer;
}
header .tag-selector-search-bar .tag-clear:not(.disabled):hover {
text-decoration: underline;
}
header .tag-selector-search-bar .tag-clear.disabled {
color: rgba(0, 120, 215, 0.3);
cursor: default;
}
header .tag-selector-search-bar .tag-and-or input[type=radio] {
margin-left: 10px;
}
header .tag-selector-search-bar .tag-and-or input[type=radio]:not(:first-child) {
margin-left: 20px;
}
header .tag-selector-search-bar .tag-list-item input[type=checkbox] {
margin: 0;
}
header .status-selector {
position: absolute;
top: 184px;
right: 128px;
width: 110px;
background: white;
box-shadow: 0px 0px 5px 0px rgba(102, 102, 102, 0.3);
z-index: 100;
display: none;
}
header .status-selector .status-item {
height: 32px;
line-height: 32px;
padding: 0 15px;
font-size: 14px;
cursor: default;
}
header .status-selector .status-item:hover {
background: rgba(0, 120, 215, 0.1);
}
header .status-selector .status-item:active {
background: rgba(0, 120, 215, 0.2);
}
header .search-bar button.dropdown.status {
width: 90px;
}
#main.show-search-bar header.show-tag-selector .tag-selector,
#main.show-search-bar header.show-tag-selector .tag-selector-search-bar {
display: block;
}
#main.show-search-bar header.show-tag-selector .search-bar .tags {
background: rgba(0, 120, 215, 0.1);
}
#main.show-search-bar header.show-status-selector .status-selector {
display: block;
}
#main.show-search-bar header.show-status-selector .search-bar .status {
background: rgba(0, 120, 215, 0.1);
}
button.tags,
button.status {
display: none;
}
.content {
margin: 0 18px 0 8px;
height: calc(100% - 152px);
}
#main.show-search-bar:not(.show-search-result) .content {
height: calc(100% - 189px);
}
#main.show-search-result:not(.show-search-bar) .content {
height: calc(100% - 184px);
}
#main.show-search-bar.show-search-result .content {
height: calc(100% - 221px);
}
.content table {
width: 100%;
height: 100%;
border-collapse: collapse;
box-sizing: border-box;
display: block;
font-size: 0;
}
.content table thead,
.content table tbody {
display: block;
}
.content table tr {
display: block;
height: 32px;
border-bottom: 1px solid rgba(102, 102, 102, 0.1);
box-sizing: border-box;
}
.content table tr:not(:last-child):not(.loading-digital-twin-files) {
cursor: pointer;
}
.content table th {
color: #666;
font-weight: normal;
}
.content table th,
.content table td {
height: 31px;
line-height: 31px;
padding: 0 10px;
font-size: 12px;
text-align: left;
box-sizing: border-box;
display: inline-block;
position: relative;
}
.content table th,
.content table td .td_inner {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.content table td .td_inner {
width: 100%;
}
.content table th:nth-child(1),
.content table td:nth-child(1) {
width: 20px;
padding: 0;
}
.content table th:nth-child(2),
.content table td:nth-child(2) {
/* width: calc(23% - 52px);
max-width: 74px; */
width: calc(20% - 20px);
}
.content table th:nth-child(3),
.content table td:nth-child(3) {
/* width: calc(46% - 104px);
max-width: 150px; */
width: calc(50% - 50px);
}
.content table th:nth-child(4),
.content table td:nth-child(4) {
/* width: 120px; */
width: calc(15% - 15px);
/* text-align: right; */
}
.content table th:nth-child(5),
.content table td:nth-child(5) {
/* min-width: calc(30% - 70px);
width: calc(100% - 464px); */
width: calc(15% + 65px);
}
.content table tbody {
max-height: calc(100% - 32px);
margin-right: -10px;
margin-top: -1px;
overflow-y: scroll;
}
.content table tbody tr:last-child {
height: 12px;
background: url(image/More.svg) no-repeat center;
background-size: 16px 16px;
}
.content table tbody tr:last-child td {
height: 11px;
}
.content table tbody::-webkit-scrollbar {
width: 10px;
background: #eee;
}
.content table tbody::-webkit-scrollbar-thumb {
background-color: #ccc;
border: 2px solid #eee;
border-radius: 5px;
}
.content table tbody tr:not(:last-child):not(.loading-digital-twin-files):hover {
background: rgba(0, 120, 215, 0.1);
}
.content table tbody tr:not(:last-child):not(.loading-digital-twin-files):active {
background: rgba(0, 120, 215, 0.2);
}
.content table tr.loading-digital-twin-files {
cursor: default;
}
.content table tr.loading-digital-twin-files td::before {
content: "";
display: block;
height: 8px;
background: rgba(102, 102, 102, 0.2);
bottom: 11px;
position: absolute;
}
.content table tr.loading-digital-twin-files td:nth-child(1)::before {
height: 16px;
width: 16px;
border: 1px solid #c8c8c8;
background: none;
bottom: 6px;
left: 1px;
}
.content table tr.loading-digital-twin-files td:nth-child(2)::before {
width: 80px;
}
.content table tr.loading-digital-twin-files td:nth-child(3)::before {
width: 120px;
}
.content table tr.loading-digital-twin-files td:nth-child(4)::before {
width: 70px;
/* right: 10px; */
}
.content table tr.loading-digital-twin-files td:nth-child(5)::before {
width: 40px;
}
.content table tr.loading-digital-twin-files td:nth-child(6)::before {
width: 40px;
}
.copy_icon {
width: 32px;
height: 32px;
position: absolute;
bottom: 0;
right: 0;
display: none;
z-index: 10;
}
.copy_icon::after {
content: "";
width: 16px;
height: 16px;
display: block;
-webkit-mask-image: url(image/Copy.svg);
mask-image: url(image/Copy.svg);
-webkit-mask-size: 16px 16px;
mask-size: 16px 16px;
background-color: #333;
left: 8px;
top: 8px;
position: absolute;
}
:hover > .copy_icon {
display: block;
}
.copy_icon:hover {
background-color: rgba(0, 120, 215, 0.2);
}
.copy_icon.copied::before {
content: "Copied";
display: block;
background: rgba(0, 0, 0, 0.5);
color: white;
text-align: center;
font-size: 10px;
height: 20px;
line-height: 20px;
width: 50px;
position: absolute;
right: -55px;
top: 6px;
}
/* .copy_icon.copied::after {
content: "";
display: block;
border-right: 5px solid rgba(0, 0, 0, 0.5);
border-top: 5px solid transparent;
border-bottom: 5px solid transparent;
position: absolute;
right: -5px;
top: 11px;
} */
.copy-text-box {
position: absolute;
top: -1000px;
left: 0;
}
.info_icon {
width: 16px;
height: 16px;
display: inline-block;
-webkit-mask-image: url(image/Info.svg);
mask-image: url(image/Info.svg);
background-color: #333;
vertical-align: text-bottom;
margin: 0 0.6em;
}
.search_result {
height: 32px;
line-height: 32px;
}
.search_result .link {
text-decoration: underline;
cursor: pointer;
}
body.vscode-dark {
color: #ccc;
background: #1e1e1e;
}
body.vscode-dark em {
background-color: rgba(255, 255, 255, 0.2);
}
body.vscode-dark .with-icon::before {
background-color: #ccc;
}
body.vscode-dark button:not(:disabled):hover {
background: rgba(255, 255, 255, 0.1);
}
body.vscode-dark button:not(:disabled):active {
background: rgba(255, 255, 255, 0.2)!important;
}
body.vscode-dark button:disabled {
color: rgba(255, 255, 255, 0.3);
}
body.vscode-dark button:disabled::before {
background-color: rgba(255, 255, 255, 0.3);
}
body.vscode-dark button {
color: #eee;
}
body.vscode-dark .content table th {
color: #888;
}
body.vscode-dark #main.show-search-bar header .action-bar button.filter {
background: rgba(255, 255, 255, 0.2);
}
body.vscode-dark #main.show-search-bar header.show-status-selector .search-bar .status {
background: rgba(255, 255, 255, 0.1);
}
body.vscode-dark #main.show-search-bar header.show-tag-selector .search-bar .tags {
background: rgba(255, 255, 255, 0.1);
}
body.vscode-dark header .search-bar input[type=text] {
color: #ccc;
}
body.vscode-dark header .search-bar button.dropdown::after {
background-color: #eee;
}
body.vscode-dark header .status-selector {
background: #333;
}
body.vscode-dark header .tag-selector {
border-top-color: #333;
background: #333;
}
body.vscode-dark header .tag-selector .tag-list-item:hover {
background: rgba(255, 255, 255, 0.1);
}
body.vscode-dark header .tag-selector .tag-list-item:active {
background: rgba(255, 255, 255, 0.2);
}
body.vscode-dark header .status-selector .status-item:hover {
background: rgba(255, 255, 255, 0.1);
}
body.vscode-dark header .status-selector .status-item:active {
background: rgba(255, 255, 255, 0.2);
}
body.vscode-dark header .tag-selector-search-bar .tag-search-bar input[type=text] {
color: #ccc;
}
body.vscode-dark .content table tr.loading-digital-twin-files td:not(:first-child)::before {
background: rgba(255, 255, 255, 0.2);
}
body.vscode-dark .content table tr {
border-bottom-color: rgba(255, 255, 255, 0.1)
}
body.vscode-dark .content table tbody tr:not(:last-child):not(.loading-digital-twin-files):hover {
background: rgba(255, 255, 255, 0.1);
}
body.vscode-dark .content table tbody tr:not(:last-child):not(.loading-digital-twin-files):active {
background: rgba(255, 255, 255, 0.2);
}
body.vscode-dark .content table tbody::-webkit-scrollbar {
background: #333;
}
body.vscode-dark .content table tbody::-webkit-scrollbar-thumb {
background-color: #1e1e1e;
border-color: #333;
}
body.vscode-dark .copy_icon::after {
background-color: #ccc;
}
body.vscode-dark .copy_icon:hover {
background-color: rgba(255, 255, 255, 0.2);
}
body.vscode-dark .info_icon {
background-color: #ccc;
}

10947
assets/modelRepository/vue.js Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Двоичные данные
logo.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 6.9 KiB

1932
package-lock.json сгенерированный

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -1,28 +1,78 @@
{
"name": "iot-pnp",
"displayName": "iot-pnp",
"description": "IoT Plug and Play",
"name": "azure-digital-twins",
"displayName": "IoT Plug and Play",
"description": "Author IoT Plug and Play models, publish and manage with Model Repository",
"version": "0.0.1",
"publisher": "vsciot-vscode",
"aiKey": "5b869bc6-ca93-4f24-aa87-92871a3a616e",
"icon": "logo.png",
"license": "SEE LICENSE IN LICENSE.txt",
"engines": {
"vscode": "^1.36.0"
},
"repository": {
"type": "git",
"url": "https://github.com/Microsoft/vscode-azure-digital-twins.git"
},
"bugs": {
"url": "https://github.com/Microsoft/vscode-azure-digital-twins/issues"
},
"homepage": "https://github.com/Microsoft/vscode-azure-digital-twins/blob/master/README.md",
"categories": [
"Azure",
"Other"
],
"activationEvents": [
"onCommand:extension.helloWorld"
"onCommand:azure-digital-twins.createInterface",
"onCommand:azure-digital-twins.createCapabilityModel",
"onCommand:azure-digital-twins.openRepository",
"onCommand:azure-digital-twins.signOutRepository",
"onCommand:azure-digital-twins.submitFiles"
],
"main": "./out/extension.js",
"main": "./out/extension",
"contributes": {
"commands": [
{
"command": "extension.helloWorld",
"title": "Hello World"
"command": "azure-digital-twins.createInterface",
"title": "Create Interface",
"category": "IoT Plug and Play"
},
{
"command": "azure-digital-twins.createCapabilityModel",
"title": "Create Capability Model",
"category": "IoT Plug and Play"
},
{
"command": "azure-digital-twins.openRepository",
"title": "Open Model Repository",
"category": "IoT Plug and Play"
},
{
"command": "azure-digital-twins.signOutRepository",
"title": "Sign out Model Repository",
"category": "IoT Plug and Play"
},
{
"command": "azure-digital-twins.submitFiles",
"title": "Submit Files to Model Repository",
"category": "IoT Plug and Play"
}
],
"configuration": [
{
"title": "IoT Plug and Play Configuration",
"properties": {
"iot-pnp.publicRepositoryUrl": {
"type": "string",
"default": "https://repo.azureiotrepository.com",
"description": "Set the public model repository url."
}
}
}
],
"languages": [
{
"id": "pnp-channel",
"id": "colorized-channel",
"mimetypes": [
"text/x-code-output"
]
@ -30,9 +80,9 @@
],
"grammars": [
{
"language": "pnp-channel",
"scopeName": "text.channel.pnp",
"path": "./syntaxes/pnp.channel.tmLanguage"
"language": "colorized-channel",
"scopeName": "text.channel.colorized",
"path": "./syntaxes/colorized.channel.tmLanguage"
}
]
},
@ -40,18 +90,29 @@
"vscode:prepublish": "npm run compile",
"compile": "tsc -p ./",
"watch": "tsc -watch -p ./",
"pretest": "npm run compile",
"tslint": "tslint -t verbose src/**/*.ts",
"test": "node ./out/test/runTest.js"
},
"devDependencies": {
"@types/fs-extra": "^7.0.0",
"@types/glob": "^7.1.1",
"@types/mocha": "^5.2.6",
"@types/node": "^10.12.21",
"@types/vscode": "^1.36.0",
"cz-conventional-changelog": "^3.0.2",
"glob": "^7.1.4",
"mocha": "^6.1.4",
"typescript": "^3.3.1",
"tslint": "^5.12.1",
"typescript": "^3.3.1",
"vscode-test": "^1.0.0-next.0"
},
"dependencies": {
"vscode-extension-telemetry": "^0.1.0",
"fs-extra": "^7.0.1"
},
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
}
}

Просмотреть файл

@ -0,0 +1,16 @@
{
"@id": "{DigitalTwinIdentifier}",
"@type": "CapabilityModel",
"displayName": "mycapabilitymodel",
"implements": [
{
"schema": "urn:azureiot:DeviceManagement:DeviceInformation:1",
"name": "deviceInfo"
},
{
"schema": "urn:azureiot:ModelDiscovery:ModelDefinition:1",
"name": "modelDefinition"
}
],
"@context": "http://azureiot.com/v1/contexts/IoTModel.json"
}

Просмотреть файл

@ -0,0 +1,74 @@
{
"@id": "{DigitalTwinIdentifier}",
"@type": "Interface",
"displayName": "myinterface",
"contents": [
{
"@type": "Property",
"displayName": "Device Name",
"description": "The name of the device.",
"name": "name",
"schema": "string",
"writable": false
},
{
"@type": "Property",
"name": "fanSpeed",
"displayName": "Fan Speed",
"writable": true,
"schema": "double"
},
{
"@type": "Telemetry",
"comment": "This shows an event that contains a single value (temperature).",
"name": "temperature",
"schema": "double"
},
{
"@type": "Telemetry",
"name": "magnetometer",
"displayName": "Magnetometer",
"comment": "This shows a complex telemetry that contains a magnetometer reading.",
"schema": {
"@type": "Object",
"fields": [
{
"name": "x",
"schema": "integer"
},
{
"name": "y",
"schema": "integer"
},
{
"name": "z",
"schema": "integer"
}
]
}
},
{
"@type": "Command",
"description": "This command will begin blinking the LED for given time interval.",
"name": "blink",
"commandType": "synchronous",
"request": {
"name": "interval",
"schema": "long"
},
"response": {
"name": "blinkResponse",
"schema": {
"@type": "Object",
"fields": [
{
"name": "description",
"schema": "string"
}
]
}
}
}
],
"@context": "http://azureiot.com/v1/contexts/IoTModel.json"
}

Просмотреть файл

@ -0,0 +1,44 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import * as vscode from "vscode";
export class ColorizedChannel {
private static buildTag(name: string | undefined): string {
return name ? `[${name}]` : "";
}
private channel: vscode.OutputChannel;
constructor(name: string) {
this.channel = vscode.window.createOutputChannel(name);
}
public start(message: string, component?: string): void {
const tag = ColorizedChannel.buildTag(component);
this.channel.appendLine(`[Start]${tag} ${message}`);
}
public end(message: string, component?: string): void {
const tag = ColorizedChannel.buildTag(component);
this.channel.appendLine(`[Done]${tag} ${message}`);
}
public warn(message: string, component?: string): void {
const tag = ColorizedChannel.buildTag(component);
this.channel.appendLine(`[Warn]${tag} ${message}`);
}
public error(message: string, component?: string): void {
const tag = ColorizedChannel.buildTag(component);
this.channel.appendLine(`[Error]${tag} ${message}`);
}
public info(message: string): void {
this.channel.appendLine(message);
}
public show(): void {
this.channel.show();
}
}

21
src/common/command.ts Normal file
Просмотреть файл

@ -0,0 +1,21 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
export class Command {
public static readonly CREATE_INTERFACE = new Command("azure-digital-twins.createInterface", "Create Interface");
public static readonly CREATE_CAPABILITY_MODEL = new Command(
"azure-digital-twins.createCapabilityModel",
"Create Capability Model",
);
public static readonly OPEN_REPOSITORY = new Command("azure-digital-twins.openRepository", "Open Model Repository");
public static readonly SIGN_OUT_REPOSITORY = new Command(
"azure-digital-twins.signOutRepository",
"Sign out Model Repository",
);
public static readonly SUBMIT_FILES = new Command(
"azure-digital-twins.submitFiles",
"Submit Files to Model Repository",
);
private constructor(public readonly id: string, public readonly description: string) {}
}

Просмотреть файл

@ -1,4 +1,22 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
export const EXTENSION_NAME = "IoT Plug and Play";
export class Constants {
public static readonly CHANNEL_NAME = "IoT Plug and Play";
public static readonly UTF8 = "utf8";
public static readonly RESOURCE_FOLDER = "resources";
public static readonly TEMPLATE_FOLDER = "templates";
public static readonly SAMPLE_FILENAME = "sample";
public static readonly DEVICE_MODEL_COMPONENT = "Device Model";
public static readonly MODEL_REPOSITORY_COMPONENT = "Model Repository";
public static readonly EXTENSION_ACTIVATED_MSG = "extensionActivated";
public static readonly NSAT_SURVEY_URL = "https://aka.ms/vscode-azure-digital-twins-survey";
public static readonly MODEL_NAME_REGEX = new RegExp("^[a-zA-Z_][a-zA-Z0-9_]*$");
public static readonly MODEL_NAME_REGEX_DESCRIPTION = "alphanumeric and underscore, not start with number";
public static readonly DIGITAL_TWIN_ID_PLACEHOLDER = "{DigitalTwinIdentifier}";
}

99
src/common/nsat.ts Normal file
Просмотреть файл

@ -0,0 +1,99 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { commands, ExtensionContext, Memento, Uri, window } from "vscode";
import { TelemetryClient } from "./telemetryClient";
const PROBABILITY = 1;
const SESSION_COUNT_THRESHOLD = 2;
const UNKNOWN = "unknown";
const PACKAGE_JSON_PATH = "./package.json";
const SESSION_COUNT_KEY = "nsat/sessionCount";
const LAST_SESSION_DATE_KEY = "nsat/lastSessionDate";
const TAKE_SURVEY_DATE_KEY = "nsat/takeSurveyDate";
const DONT_SHOW_DATE_KEY = "nsat/dontShowDate";
const SKIP_VERSION_KEY = "nsat/skipVersion";
const IS_CANDIDATE_KEY = "nsat/isCandidate";
export class NSAT {
constructor(private readonly surveyUrl: string, private readonly telemetryClient: TelemetryClient) {}
public async takeSurvey(context: ExtensionContext) {
const packageJSON = require(context.asAbsolutePath(PACKAGE_JSON_PATH));
if (!packageJSON) {
return;
}
const globalState: Memento = context.globalState;
if (!globalState) {
return;
}
const skipVersion: string = globalState.get(SKIP_VERSION_KEY, "");
if (skipVersion) {
return;
}
const date: string = new Date().toDateString();
const lastSessionDate: string = globalState.get(LAST_SESSION_DATE_KEY, new Date(0).toDateString());
if (date === lastSessionDate) {
return;
}
const sessionCount: number = globalState.get(SESSION_COUNT_KEY, 0) + 1;
await globalState.update(LAST_SESSION_DATE_KEY, date);
await globalState.update(SESSION_COUNT_KEY, sessionCount);
if (sessionCount < SESSION_COUNT_THRESHOLD) {
return;
}
const isCandidate: boolean = globalState.get(IS_CANDIDATE_KEY, false) || Math.random() < PROBABILITY;
await globalState.update(IS_CANDIDATE_KEY, isCandidate);
const extensionVersion: string = packageJSON.version || UNKNOWN;
if (!isCandidate) {
await globalState.update(SKIP_VERSION_KEY, extensionVersion);
return;
}
const take = {
title: "Take Survey",
run: async () => {
this.telemetryClient.sendEvent("nsat.survey/takeShortSurvey");
commands.executeCommand(
"vscode.open",
Uri.parse(
`${this.surveyUrl}?o=${encodeURIComponent(process.platform)}&v=${encodeURIComponent(extensionVersion)}`,
),
);
await globalState.update(IS_CANDIDATE_KEY, false);
await globalState.update(SKIP_VERSION_KEY, extensionVersion);
await globalState.update(TAKE_SURVEY_DATE_KEY, date);
},
};
const remind = {
title: "Remind Me Later",
run: async () => {
this.telemetryClient.sendEvent("nsat.survey/remindMeLater");
await globalState.update(SESSION_COUNT_KEY, 0);
},
};
const never = {
title: "Don't Show Again",
run: async () => {
this.telemetryClient.sendEvent("nsat.survey/dontShowAgain");
await globalState.update(IS_CANDIDATE_KEY, false);
await globalState.update(SKIP_VERSION_KEY, extensionVersion);
await globalState.update(DONT_SHOW_DATE_KEY, date);
},
};
this.telemetryClient.sendEvent("nsat.survey/userAsked");
const button = await window.showInformationMessage(
"Do you mind taking a quick feedback survey about the Azure IoT Edge Extension for VS Code?",
take,
remind,
never,
);
await (button || remind).run();
}
}

Просмотреть файл

@ -1,38 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import * as vscode from "vscode";
import * as constants from "./constants";
export const pnpChannel = {
channel: vscode.window.createOutputChannel(constants.EXTENSION_NAME),
start(component: string, message: string) {
this.channel.appendLine(`[Start][${component}] ${message}`);
},
end(component: string, message: string) {
this.channel.appendLine(`[Complete][${component}] ${message}`);
},
warn(component: string, message: string) {
this.channel.appendLine(`[Warn][${component}] ${message}`);
},
error(component: string, message: string) {
this.channel.appendLine(`[Error][${component}] ${message}`);
},
info(message: string) {
this.channel.appendLine(message);
},
show() {
this.channel.show();
},
hide() {
this.channel.hide();
}
};

Просмотреть файл

@ -0,0 +1,13 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
export class ProcessError extends Error {
constructor(message: string, public readonly component: string) {
super(message);
this.name = "ProcessError";
if (Error.captureStackTrace) {
Error.captureStackTrace(this, ProcessError);
}
}
}

Просмотреть файл

@ -0,0 +1,86 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import * as vscode from "vscode";
import TelemetryReporter from "vscode-extension-telemetry";
const MILLISECOND = 1000;
const PACKAGE_JSON_PATH = "./package.json";
const INTERNAL_USER_DOMAIN = "microsoft.com";
export interface TelemetryContext {
start: number;
properties: { [key: string]: string };
measurements: { [key: string]: number };
}
export enum TelemetryResult {
Succeeded = "Succeeded",
Failed = "Failed",
Cancelled = "Cancelled",
}
export class TelemetryClient {
private static validatePackageJSON(packageJSON: any): boolean {
return packageJSON.name && packageJSON.publisher && packageJSON.version && packageJSON.aiKey;
}
private static isInternalUser(): boolean {
const userDomain = process.env.USERDNSDOMAIN ? process.env.USERDNSDOMAIN.toLowerCase() : "";
return userDomain.endsWith(INTERNAL_USER_DOMAIN);
}
private client: TelemetryReporter | undefined;
private isInternal: boolean = false;
constructor(context: vscode.ExtensionContext) {
const packageJSON = require(context.asAbsolutePath(PACKAGE_JSON_PATH));
if (!packageJSON) {
return;
}
if (!TelemetryClient.validatePackageJSON(packageJSON)) {
return;
}
this.client = new TelemetryReporter(
`${packageJSON.publisher}.${packageJSON.name}`,
packageJSON.version,
packageJSON.aiKey,
);
this.isInternal = TelemetryClient.isInternalUser();
}
public sendEvent(eventName: string, telemetryContext?: TelemetryContext): void {
if (!this.client) {
return;
}
if (telemetryContext) {
this.client.sendTelemetryEvent(eventName, telemetryContext.properties, telemetryContext.measurements);
} else {
this.client.sendTelemetryEvent(eventName);
}
}
public createContext(): TelemetryContext {
const context: TelemetryContext = { start: Date.now(), properties: {}, measurements: {} };
context.properties.isInternal = this.isInternal.toString();
context.properties.result = TelemetryResult.Succeeded;
return context;
}
public setErrorContext(context: TelemetryContext, error: Error): void {
context.properties.result = TelemetryResult.Failed;
context.properties.error = error.name;
context.properties.errorMessage = error.message;
}
public setCancelContext(context: TelemetryContext): void {
context.properties.result = TelemetryResult.Cancelled;
}
public closeContext(context: TelemetryContext) {
context.measurements.duration = (Date.now() - context.start) / MILLISECOND;
}
}

Просмотреть файл

@ -0,0 +1,10 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
export class UserCancelledError extends Error {
constructor(operation?: string) {
const message = operation ? ` on [${operation}]` : "";
super("User cancelled the operation" + message);
this.name = "UserCancelledError";
}
}

46
src/common/utility.ts Normal file
Просмотреть файл

@ -0,0 +1,46 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import * as fs from "fs-extra";
import * as path from "path";
import { DeviceModelManager, ModelType } from "../deviceModel/deviceModelManager";
import { Constants } from "./constants";
export class Utility {
public static async createFileFromTemplate(
templatePath: string,
filePath: string,
replacement: Map<string, string>,
): Promise<void> {
const template: string = await fs.readFile(templatePath, Constants.UTF8);
const content: string = Utility.replaceAll(template, replacement);
await fs.writeFile(filePath, content, { encoding: Constants.UTF8 });
}
public static replaceAll(str: string, replacement: Map<string, string>, caseInsensitive: boolean = false): string {
const flag: string = caseInsensitive ? "ig" : "g";
const keys = Array.from(replacement.keys());
const pattern: RegExp = new RegExp(keys.join("|"), flag);
return str.replace(pattern, (matched) => {
const value: string | undefined = replacement.get(matched);
return value ? value : matched;
});
}
public static async validateModelName(name: string, type: ModelType, folder?: string): Promise<string | undefined> {
if (!name || name.trim() === "") {
return "Name could not be empty";
}
if (!Constants.MODEL_NAME_REGEX.test(name)) {
return `Name can only contain ${Constants.MODEL_NAME_REGEX_DESCRIPTION}`;
}
if (folder) {
const filename = DeviceModelManager.generateModelFilename(name, type);
if (await fs.pathExists(path.join(folder, filename))) {
return `${type} ${name} already exists in folder ${folder}`;
}
}
return undefined;
}
}

Просмотреть файл

@ -0,0 +1,66 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import * as path from "path";
import * as vscode from "vscode";
import { ColorizedChannel } from "../common/colorizedChannel";
import { Constants } from "../common/constants";
import { ProcessError } from "../common/processError";
import { Utility } from "../common/utility";
import { MessageType, UI } from "../views/ui";
import { UIConstants } from "../views/uiConstants";
export enum ModelType {
Interface = "Interface",
CapabilityModel = "Capability Model",
}
export class DeviceModelManager {
public static generateModelId(name: string): string {
return `urn:{your name}:${name}:1`;
}
public static generateModelFilename(name: string, type: ModelType): string {
const fileType: string = type.replace(/\s+/g, "").toLowerCase();
return `${name}.${fileType}.json`;
}
public static getTemplateFilename(type: ModelType): string {
return DeviceModelManager.generateModelFilename(Constants.SAMPLE_FILENAME, type);
}
constructor(private readonly context: vscode.ExtensionContext, private readonly outputChannel: ColorizedChannel) {}
public async createModel(type: ModelType): Promise<void> {
const folder: string = await UI.selectRootFolder(UIConstants.SELECT_ROOT_FOLDER_LABEL);
const name: string = await UI.inputModelName(UIConstants.INPUT_MODEL_NAME_LABEL, type, folder);
const model: string = `${type} ${name}`;
this.outputChannel.start(`Create ${model} in folder ${folder}`, Constants.DEVICE_MODEL_COMPONENT);
let filePath: string;
try {
filePath = await this.doCreateModel(type, folder, name);
} catch (error) {
const errorMessage = `Fail to create ${model}. Error: ${error.message}`;
throw new ProcessError(errorMessage, Constants.DEVICE_MODEL_COMPONENT);
}
const message = `${model} is created successfully`;
await UI.openAndShowTextDocument(filePath);
UI.showNotification(MessageType.Info, message);
this.outputChannel.end(message, Constants.DEVICE_MODEL_COMPONENT);
}
private async doCreateModel(type: ModelType, folder: string, name: string): Promise<string> {
const modelId = DeviceModelManager.generateModelId(name);
const filePath = path.join(folder, DeviceModelManager.generateModelFilename(name, type));
const templatePath = this.context.asAbsolutePath(
path.join(Constants.RESOURCE_FOLDER, Constants.TEMPLATE_FOLDER, DeviceModelManager.getTemplateFilename(type)),
);
const replacement = new Map<string, string>();
replacement.set(Constants.DIGITAL_TWIN_ID_PLACEHOLDER, modelId);
await Utility.createFileFromTemplate(templatePath, filePath, replacement);
return filePath;
}
}

Просмотреть файл

@ -1,39 +1,92 @@
// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import * as vscode from "vscode";
import { ColorizedChannel } from "./common/colorizedChannel";
import { Command } from "./common/command";
import { Constants } from "./common/constants";
import { NSAT } from "./common/nsat";
import { ProcessError } from "./common/processError";
import { TelemetryClient, TelemetryContext } from "./common/telemetryClient";
import { UserCancelledError } from "./common/userCancelledError";
import { DeviceModelManager, ModelType } from "./deviceModel/deviceModelManager";
import { MessageType, UI } from "./views/ui";
import * as constants from "./common/constants";
import { pnpChannel } from "./common/outputChannel";
// this method is called when your extension is activated
// your extension is activated the very first time the command is executed
export function activate(context: vscode.ExtensionContext) {
// Use the console to output diagnostic information (console.log) and errors (console.error)
// This line of code will only be executed once when your extension is activated
console.log('Congratulations, your extension "iot-pnp" is now active!');
const outputChannel = new ColorizedChannel(Constants.CHANNEL_NAME);
const telemetryClient = new TelemetryClient(context);
const nsat = new NSAT(Constants.NSAT_SURVEY_URL, telemetryClient);
const deviceModelManager = new DeviceModelManager(context, outputChannel);
// The command has been defined in the package.json file
// Now provide the implementation of the command with registerCommand
// The commandId parameter must match the command field in package.json
let disposable = vscode.commands.registerCommand(
"extension.helloWorld",
() => {
// The code you place here will be executed every time your command is executed
telemetryClient.sendEvent(Constants.EXTENSION_ACTIVATED_MSG);
// Display a message box to the user
let message = "Hello World!";
pnpChannel.start(constants.EXTENSION_NAME, message);
pnpChannel.end(constants.EXTENSION_NAME, message);
pnpChannel.info(message);
pnpChannel.warn(constants.EXTENSION_NAME, message);
pnpChannel.error(constants.EXTENSION_NAME, message);
pnpChannel.show();
}
initCommand(
context,
telemetryClient,
outputChannel,
nsat,
true,
Command.CREATE_INTERFACE,
(): Promise<void> => {
return deviceModelManager.createModel(ModelType.Interface);
},
);
context.subscriptions.push(disposable);
initCommand(
context,
telemetryClient,
outputChannel,
nsat,
true,
Command.CREATE_CAPABILITY_MODEL,
(): Promise<void> => {
return deviceModelManager.createModel(ModelType.CapabilityModel);
},
);
}
// this method is called when your extension is deactivated
export function deactivate() {}
function initCommand(
context: vscode.ExtensionContext,
telemetryClient: TelemetryClient,
outputChannel: ColorizedChannel,
nsat: NSAT,
enableSurvey: boolean,
command: Command,
callback: (...args: any[]) => Promise<any>,
): void {
context.subscriptions.push(
vscode.commands.registerCommand(command.id, async (...args: any[]) => {
const telemetryContext: TelemetryContext = telemetryClient.createContext();
telemetryClient.sendEvent(`${command.id}.start`);
outputChannel.show();
outputChannel.start(`Trigger command ${command.description}`);
try {
return await callback(...args);
} catch (error) {
if (error instanceof UserCancelledError) {
outputChannel.warn(error.message);
} else {
telemetryClient.setErrorContext(telemetryContext, error);
UI.showNotification(MessageType.Error, error.message);
if (error instanceof ProcessError) {
const message = `${error.message}\nStack: ${error.stack}`;
outputChannel.error(message, error.component);
} else {
outputChannel.error(error.message);
}
}
} finally {
telemetryClient.closeContext(telemetryContext);
telemetryClient.sendEvent(`${command.id}.end`, telemetryContext);
outputChannel.end(`Complete command ${command.description}`);
if (enableSurvey) {
nsat.takeSurvey(context);
}
}
}),
);
}

Просмотреть файл

@ -1,23 +1,23 @@
import * as path from 'path';
import * as path from "path";
import { runTests } from 'vscode-test';
import { runTests } from "vscode-test";
async function main() {
try {
// The folder containing the Extension Manifest package.json
// Passed to `--extensionDevelopmentPath`
const extensionDevelopmentPath = path.resolve(__dirname, '../../');
try {
// The folder containing the Extension Manifest package.json
// Passed to `--extensionDevelopmentPath`
const extensionDevelopmentPath = path.resolve(__dirname, "../../");
// The path to test runner
// Passed to --extensionTestsPath
const extensionTestsPath = path.resolve(__dirname, './suite/index');
// The path to test runner
// Passed to --extensionTestsPath
const extensionTestsPath = path.resolve(__dirname, "./suite/index");
// Download VS Code, unzip it and run the integration test
await runTests({ extensionDevelopmentPath, extensionTestsPath });
} catch (err) {
console.error('Failed to run tests');
process.exit(1);
}
// Download VS Code, unzip it and run the integration test
await runTests({ extensionDevelopmentPath, extensionTestsPath });
} catch (err) {
console.error("Failed to run tests");
process.exit(1);
}
}
main();

Просмотреть файл

@ -1,18 +1,18 @@
import * as assert from 'assert';
import { before } from 'mocha';
import * as assert from "assert";
import { before } from "mocha";
// You can import and use all API from the 'vscode' module
// as well as import your extension to test it
import * as vscode from 'vscode';
import * as vscode from "vscode";
// import * as myExtension from '../extension';
suite('Extension Test Suite', () => {
before(() => {
vscode.window.showInformationMessage('Start all tests.');
});
suite("Extension Test Suite", () => {
before(() => {
vscode.window.showInformationMessage("Start all tests.");
});
test('Sample test', () => {
assert.equal(-1, [1, 2, 3].indexOf(5));
assert.equal(-1, [1, 2, 3].indexOf(0));
});
test("Sample test", () => {
assert.equal(-1, [1, 2, 3].indexOf(5));
assert.equal(-1, [1, 2, 3].indexOf(0));
});
});

Просмотреть файл

@ -1,37 +1,37 @@
import * as path from 'path';
import * as Mocha from 'mocha';
import * as glob from 'glob';
import * as glob from "glob";
import * as Mocha from "mocha";
import * as path from "path";
export function run(): Promise<void> {
// Create the mocha test
const mocha = new Mocha({
ui: 'tdd',
});
mocha.useColors(true);
// Create the mocha test
const mocha = new Mocha({
ui: "tdd",
});
mocha.useColors(true);
const testsRoot = path.resolve(__dirname, '..');
const testsRoot = path.resolve(__dirname, "..");
return new Promise((c, e) => {
glob('**/**.test.js', { cwd: testsRoot }, (err, files) => {
if (err) {
return e(err);
}
return new Promise((c, e) => {
glob("**/**.test.js", { cwd: testsRoot }, (err, files) => {
if (err) {
return e(err);
}
// Add files to the test suite
files.forEach(f => mocha.addFile(path.resolve(testsRoot, f)));
// Add files to the test suite
files.forEach((f) => mocha.addFile(path.resolve(testsRoot, f)));
try {
// Run the mocha test
mocha.run(failures => {
if (failures > 0) {
e(new Error(`${failures} tests failed.`));
} else {
c();
}
});
} catch (err) {
e(err);
}
});
});
try {
// Run the mocha test
mocha.run((failures) => {
if (failures > 0) {
e(new Error(`${failures} tests failed.`));
} else {
c();
}
});
} catch (err) {
e(err);
}
});
});
}

121
src/views/ui.ts Normal file
Просмотреть файл

@ -0,0 +1,121 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import * as path from "path";
import * as vscode from "vscode";
import { UserCancelledError } from "../common/userCancelledError";
import { Utility } from "../common/utility";
import { ModelType } from "../deviceModel/deviceModelManager";
export enum MessageType {
Info,
Warn,
Error,
}
export class UI {
public static async openAndShowTextDocument(filePath: string): Promise<void> {
const folder: string = path.dirname(filePath);
await vscode.commands.executeCommand("vscode.openFolder", vscode.Uri.file(folder), false);
await vscode.window.showTextDocument(vscode.Uri.file(filePath));
}
public static showNotification(type: MessageType, message: string): void {
switch (type) {
case MessageType.Info:
vscode.window.showInformationMessage(message);
break;
case MessageType.Warn:
vscode.window.showWarningMessage(message);
break;
case MessageType.Error:
vscode.window.showErrorMessage(message);
break;
default:
}
}
public static async selectRootFolder(label: string): Promise<string> {
const worksapceFolders = vscode.workspace.workspaceFolders;
// use the only workspace as default
if (worksapceFolders && worksapceFolders.length === 1) {
return worksapceFolders[0].uri.fsPath;
}
// select workspace or open specified folder
let items: vscode.QuickPickItem[] = [];
if (worksapceFolders) {
items = worksapceFolders.map((f: vscode.WorkspaceFolder) => {
const fsPath = f.uri.fsPath;
return {
label: path.basename(fsPath),
description: fsPath,
};
});
}
items.push({ label: "Browse...", description: undefined });
const selected: vscode.QuickPickItem = await UI.showQuickPick(items, label);
// browse to open folder
return selected.description ? selected.description : await UI.showOpenDialog(label);
}
public static async showQuickPick(items: vscode.QuickPickItem[], label: string): Promise<vscode.QuickPickItem> {
const options: vscode.QuickPickOptions = {
placeHolder: label,
ignoreFocusOut: true,
};
const result: vscode.QuickPickItem | undefined = await vscode.window.showQuickPick(items, options);
if (!result) {
throw new UserCancelledError(label);
}
return result;
}
public static async showOpenDialog(label: string, defaultUri?: vscode.Uri): Promise<string> {
const options: vscode.OpenDialogOptions = {
openLabel: label,
defaultUri,
canSelectFiles: false,
canSelectFolders: true,
canSelectMany: false,
};
const result: vscode.Uri[] | undefined = await vscode.window.showOpenDialog(options);
if (!result || result.length === 0) {
throw new UserCancelledError(label);
}
return result[0].fsPath;
}
public static async inputModelName(label: string, type: ModelType, folder: string): Promise<string> {
const placeHolder = `${type} name`;
const validateInput = async (name: string): Promise<string | undefined> => {
return await Utility.validateModelName(name, type, folder);
};
return await UI.showInputBox(label, placeHolder, validateInput);
}
public static async showInputBox(
label: string,
placeHolder: string,
validateInput?: (s: string) => Promise<string | undefined>,
value?: string,
ignoreFocusOut: boolean = true,
): Promise<string> {
const options: vscode.InputBoxOptions = {
prompt: label,
placeHolder,
validateInput,
value,
ignoreFocusOut,
};
const result: string | undefined = await vscode.window.showInputBox(options);
if (!result) {
throw new UserCancelledError(label);
}
return result;
}
}

7
src/views/uiConstants.ts Normal file
Просмотреть файл

@ -0,0 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
export class UIConstants {
public static readonly SELECT_ROOT_FOLDER_LABEL = "Select folder";
public static readonly INPUT_MODEL_NAME_LABEL = "Input device model name";
}

Просмотреть файл

@ -3,16 +3,16 @@
<plist version="1.0">
<dict>
<key>name</key>
<string>pnpChannel</string>
<string>colorizedChannel</string>
<key>scopeName</key>
<string>text.channel.pnp</string>
<string>text.channel.colorized</string>
<key>patterns</key>
<array>
<dict>
<key>name</key>
<string>channel.pnp.info</string>
<string>channel.colorized.info</string>
<key>match</key>
<string>(\[Start\]|\[Complete\])(.*)</string>
<string>(\[Start\]|\[Done\])(.*)</string>
<key>captures</key>
<dict>
<key>0</key>
@ -24,9 +24,9 @@
</dict>
<dict>
<key>name</key>
<string>channle.pnp.warn</string>
<string>channel.colorized.warn</string>
<key>match</key>
<string>(\[Warn\])(.*)</string>
<string>(\[Warn\])(.+)</string>
<key>captures</key>
<dict>
<key>0</key>
@ -38,9 +38,9 @@
</dict>
<dict>
<key>name</key>
<string>channel.pnp.error</string>
<string>channel.colorized.error</string>
<key>match</key>
<string>(\[Error\])(.*)</string>
<string>(\[Error\])(.+)</string>
<key>captures</key>
<dict>
<key>0</key>

Просмотреть файл

@ -1,15 +1,10 @@
{
"rules": {
"no-string-throw": true,
"no-unused-expression": true,
"no-duplicate-variable": true,
"curly": true,
"class-name": true,
"semicolon": [
true,
"always"
],
"triple-equals": true
},
"defaultSeverity": "warning"
"extends": "tslint:recommended",
"rules": {
"variable-name": [true, "ban-keywords", "check-format"],
"interface-name": [true, "never-prefix"],
"max-line-length": [true, 120],
"no-empty": false,
"object-literal-sort-keys": false
}
}