1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-12 13:28:33 +00:00

Merge branch 'development'

This commit is contained in:
RobertBeekman 2024-02-23 21:45:35 +01:00
commit ef08000f56
275 changed files with 805 additions and 8659 deletions

3
.gitignore vendored
View File

@ -198,4 +198,5 @@ src/Artemis\.Storage/Storage\.db
docfx/docfx_project/_site/
src/.idea/
packages.lock.json
packages.lock.json
docfx/docfx_project/api/

View File

@ -1,5 +0,0 @@
###############
# temp file #
###############
*.yml
.manifest

View File

@ -1,26 +0,0 @@
# Welcome to the **Artemis API documentation**
On this website you can browse the Artemis Core and Shared UI API.
A large part of this documentation is being generated based on code but over time the plan is to expand the documentation with written guides.
## Plugins
Artemis 2.0 has been developed from the ground up with plugins in mind. This means almost all functionality can be expanded. The following plugin types are currently available and fully implemented:
- [DeviceProvider](api/Artemis.Core.DeviceProviders.DeviceProvider.html)
- [LayerBrush\<T\>](api/Artemis.Core.LayerBrushes.LayerBrush-1.html)
- [PerLedLayerBrush\<T\>](api/Artemis.Core.LayerBrushes.PerLedLayerBrush-1.html)
- [LayerEffect](api/Artemis.Core.LayerEffects.LayerEffect-1.html)
- [Module](api/Artemis.Core.Modules.Module.html)
- [Module\<T\>](api/Artemis.Core.Modules.Module-1.html)
These allow you to expand on Artemis's functionality. For quick and interactive plugin creation, use the [Visual Studio template extension](https://marketplace.visualstudio.com/items?itemName=SpoinkyNL.ArtemisTemplates).
Example implementations of these plugins can be found on [GitHub](https://github.com/Artemis-RGB/Artemis/tree/master/src/Plugins).
## Services
Artemis provides plugins with an API through a range of services.
All the services are available to plugins by using dependency injection in your plugin's constructor. Dependency injection is also available for the different view models plugins may provide.
- [Core Services](api/Artemis.Core.Services.html#interfaces)
- [UI Services](api/Artemis.UI.Shared.Services.html#interfaces)

View File

@ -0,0 +1,4 @@
#logo {
height: 40px;
margin-right: 10px;
}

View File

@ -3,30 +3,26 @@
{
"src": [
{
"src": "../../src",
"files": [
"Artemis.Core/bin/net7.0/Artemis.Core.dll",
"Artemis.UI.Shared/bin/net7.0/Artemis.UI.Shared.dll"
],
"src": "../../src"
"**/Artemis.Core.csproj",
"**/Artemis.UI.Shared.csproj"
]
}
],
"dest": "api",
"disableGitFeatures": false,
"disableDefaultFilter": false,
"filter": "filterConfig.yml"
"filter": "filterConfig.yml",
"namespaceLayout": "nested"
}
],
"build": {
"content": [
{
"files": [
"api/**.yml"
]
},
{
"files": [
"toc.yml",
"*.md"
"**/*.{md,yml}"
],
"exclude": [
"_site/**"
]
}
],
@ -37,42 +33,23 @@
]
}
],
"overwrite": [
{
"files": [
"apidoc/**.md"
],
"exclude": [
"obj/**",
"_site/**"
]
}
"output": "_site",
"template": [
"default",
"modern",
"artemis-template"
],
"globalMetadata": {
"_appTitle": "Artemis API Documentation",
"_appName": "Artemis",
"_appFaviconPath": "images/application.ico",
"_appLogoPath": "images/application.ico",
"_appFooter": "<a href=\"https://artemis-rgb.com\" style=\"margin: 0 5px;\">\r <i class=\"bi-globe\" role=\"img\" aria-label=\"GitHub\" style=\"margin: 0 10\"></i>\r</a>\r<a href=\"https://github.com/Artemis-RGB/Artemis\" style=\"margin: 0 5px;\">\r <i class=\"bi-github\" role=\"img\" aria-label=\"GitHub\"></i>\r</a>\r<a href=\"https://artemis-rgb.com\" style=\"margin: 0 5px;\">\r <i class=\"bi bi-layout-text-sidebar-reverse\" role=\"img\" aria-label=\"GitHub\"></i>\r</a>\r<a href=\"https://wiki.artemis-rgb.com/\" style=\"margin: 0 5px;\">\r <i class=\"bi-chat-fill\" role=\"img\" aria-label=\"GitHub\"></i>\r</a>\r<a href=\"https://wiki.artemis-rgb.com/en/donating\" style=\"margin: 0 5px;\">\r <i class=\"bi-gift-fill\" role=\"img\" aria-label=\"GitHub\"></i>\r</a>",
"_appFaviconPath": "images/application.ico",
"_copyrightFooter": "Content is available under the PolyForm Noncommercial License, by Artemis RGB.",
"_enableSearch": true,
"_disableSideFilter": false,
"_enableNewTab": true,
"_disableContribution": false,
"_disableBreadcrumb": false
},
"dest": "_site",
"globalMetadataFiles": [],
"fileMetadataFiles": [],
"template": [
"default",
"templates/singulinkfx"
],
"postProcessors": [],
"markdownEngineName": "markdig",
"noLangKeyword": false,
"keepFileLink": false,
"cleanupCacheHistory": false,
"disableGitFeatures": false
}
}
}

View File

@ -24,5 +24,5 @@ apiRules:
uidRegex: ^Artemis\.UI\.Shared\.Properties
type: Type
- exclude:
uidRegex: ^Artemis\.Core\.TypeExtensions\.IsNumber
type: Method
uidRegex: ^Artemis\.Core\.TypeExtensions
type: Type

View File

@ -1,26 +1,7 @@
---
_layout: landing
---
# Welcome to the **Artemis API documentation**
On this website you can browse the Artemis Core and Shared UI API.
A large part of this documentation is being generated based on code but over time the plan is to expand the documentation with written guides.
## Plugins
Artemis 2.0 has been developed from the ground up with plugins in mind. This means almost all functionality can be expanded. The following plugin types are currently available and fully implemented:
- [DeviceProvider](api/Artemis.Core.DeviceProviders.DeviceProvider.html)
- [LayerBrush\<T\>](api/Artemis.Core.LayerBrushes.LayerBrush-1.html)
- [PerLedLayerBrush\<T\>](api/Artemis.Core.LayerBrushes.PerLedLayerBrush-1.html)
- [LayerEffect](api/Artemis.Core.LayerEffects.LayerEffect-1.html)
- [Module](api/Artemis.Core.Modules.Module.html)
- [Module\<T\>](api/Artemis.Core.Modules.Module-1.html)
These allow you to expand on Artemis's functionality. For quick and interactive plugin creation, use the [Visual Studio template extension](https://marketplace.visualstudio.com/items?itemName=SpoinkyNL.ArtemisTemplates).
Example implementations of these plugins can be found on [GitHub](https://github.com/Artemis-RGB/Artemis/tree/master/src/Plugins).
## Services
Artemis provides plugins with an API through a range of services.
All the services are available to plugins by using dependency injection in your plugin's constructor. Dependency injection is also available for the different view models plugins may provide.
- [Core Services](api/Artemis.Core.Services.html#interfaces)
- [UI Services](api/Artemis.UI.Shared.Services.html#interfaces)
# Artemis Technical Documentation
Welcome to the technical docs of Artemis. This page contains all the public types available when writing Artemis plugins.
For more details, please refer to the [Artemis wiki](https://wiki.artemis-rgb.com/en/guides/developer).

View File

@ -1,62 +0,0 @@
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
{{!include(/^styles/.*/)}}
{{!include(/^fonts/.*/)}}
{{!include(favicon.ico)}}
{{!include(logo.svg)}}
{{!include(search-stopwords.json)}}
<!DOCTYPE html>
<!--[if IE]><![endif]-->
<html>
{{>partials/head}}
<body>
<div class="top-navbar">
<a class="burger-icon" onclick="toggleMenu()">
<svg name="Hamburger"
style="vertical-align: middle;"
width="34" height="34" viewBox="0 0 24 24"><path fill="currentColor" fill-rule="evenodd" clip-rule="evenodd" d="M20 6H4V9H20V6ZM4 10.999H20V13.999H4V10.999ZM4 15.999H20V18.999H4V15.999Z"></path></svg>
</a>
{{>partials/logo}}
</div>
<div class="body-content">
<div id="blackout" class="blackout" onclick="toggleMenu()"></div>
<nav id="sidebar" role="navigation">
<div class="sidebar">
{{>partials/navbar}}
<div class="sidebar-item-separator"></div>
{{^_disableToc}}
{{>partials/toc}}
{{/_disableToc}}
</div>
{{>partials/footer}}
</nav>
<main class="main-panel">
{{#_enableSearch}}
{{>partials/searchResults}}
{{/_enableSearch}}
<div role="main" class="hide-when-search" >
{{^_disableBreadcrumb}}
{{>partials/breadcrumb}}
{{/_disableBreadcrumb}}
<article class="content wrap" id="_content" data-uid="{{uid}}">
{{!body}}
</article>
</div>
{{#_copyrightFooter}}
<div class="copyright-footer">
<span>{{_copyrightFooter}}</span>
</div>
{{/_copyrightFooter}}
</main>
</div>
{{>partials/scripts}}
</body>
</html>

View File

@ -1,4 +0,0 @@
<div class="footer">
{{{_appFooter}}}
{{^_appFooter}}<strong><a href='https://dotnet.github.io/docfx/'>DocFX</a> + <a href='https://www.singulink.com'>Singulink</a> = ♥</strong>{{/_appFooter}}
</div>

View File

@ -1,24 +0,0 @@
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>{{#title}}{{title}}{{/title}}{{^title}}{{>partials/title}}{{/title}} {{#_appTitle}}| {{_appTitle}} {{/_appTitle}}</title>
<meta name="viewport" content="width=device-width">
<meta name="title" content="{{#title}}{{title}}{{/title}}{{^title}}{{>partials/title}}{{/title}} {{#_appTitle}}| {{_appTitle}} {{/_appTitle}}">
<meta name="generator" content="docfx {{_docfxVersion}}">
{{#_description}}<meta name="description" content="{{_description}}">{{/_description}}
<link rel="shortcut icon" href="{{_rel}}{{{_appFaviconPath}}}{{^_appFaviconPath}}favicon.ico{{/_appFaviconPath}}">
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/night-owl.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.7.2/font/bootstrap-icons.css" integrity="sha384-EvBWSlnoFgZlXJvpzS+MAUEjvN7+gcCwH+qh7GRFOGgZO0PuwOFro7qPOJnLfe7l" crossorigin="anonymous">
<link rel="stylesheet" href="{{_rel}}styles/config.css">
<link rel="stylesheet" href="{{_rel}}styles/discord.css">
<link rel="stylesheet" href="{{_rel}}styles/singulink.css">
<link rel="stylesheet" href="{{_rel}}styles/main.css">
<meta property="docfx:navrel" content="{{_navRel}}">
<meta property="docfx:tocrel" content="{{_tocRel}}">
{{#_noindex}}<meta name="searchOption" content="noindex">{{/_noindex}}
{{#_enableSearch}}<meta property="docfx:rel" content="{{_rel}}">{{/_enableSearch}}
{{#_enableNewTab}}<meta property="docfx:newtab" content="true">{{/_enableNewTab}}
</head>

View File

@ -1,31 +0,0 @@
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
<ul class="nav level{{level}}">
{{#items}}
{{^dropdown}}
<li>
{{^leaf}}
<span class="expand-stub"></span>
{{/leaf}}
{{#topicHref}}
<a href="{{topicHref}}" class="sidebar-item" name="{{tocHref}}" title="{{name}}">{{name}}</a>
{{/topicHref}}
{{^topicHref}}
<a>{{{name}}}</a>
{{/topicHref}}
{{^leaf}}
{{>partials/li}}
{{/leaf}}
</li>
{{/dropdown}}
{{#dropdown}}
<li class="dropdown">
<a class="dropdown-toggle" data-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="false">{{name}} <span class="caret"></span></a>
<ul class="dropdown-menu level{{level}}">
{{>partials/dd-li}}
</ul>
</li>
{{/dropdown}}
{{/items}}
</ul>

View File

@ -1,6 +0,0 @@
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
<a class="brand" href="{{_rel}}index.html">
<img src="{{_rel}}{{{_appLogoPath}}}{{^_appLogoPath}}logo.svg{{/_appLogoPath}}" alt="{{_appName}}" class="logomark">
<span class="brand-title">{{_appName}}</span>
</a>

View File

@ -1,13 +0,0 @@
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
<h1 id="{{id}}" data-uid="{{uid}}" class="text-break">{{>partials/title}}</h1>
<div class="markdown level0 summary">{{{summary}}}</div>
<div class="markdown level0 conceptual">{{{conceptual}}}</div>
<div class="markdown level0 remarks">{{{remarks}}}</div>
{{#children}}
<h3 id="{{id}}">{{>partials/namespaceSubtitle}}</h3>
{{#children}}
<h5><xref uid="{{uid}}" altProperty="fullName" displayProperty="name"/></h5>
<section>{{{summary}}}</section>
{{/children}}
{{/children}}

View File

@ -1,19 +0,0 @@
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
<div>
<div class="mobile-hide">
{{>partials/logo}}
</div>
{{#_enableSearch}}
<div class="sidesearch">
<form id="search" role="search" class="search">
<i class="bi bi-search search-icon"></i>
<input type="text" id="search-query" placeholder="{{__global.search}}" autocomplete="off">
</form>
</div>
{{/_enableSearch}}
<div id="navbar">
</div>
</div>

View File

@ -1,12 +0,0 @@
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
<script src="https://code.jquery.com/jquery-3.5.1.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/highlight.min.js"></script>
<script type="text/javascript" src="{{_rel}}styles/jquery.twbsPagination.js"></script>
<script type="text/javascript" src="{{_rel}}styles/url.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/anchor-js/anchor.min.js"></script>
<script type="text/javascript" src="{{_rel}}styles/docfx.js"></script>
<script type="text/javascript" src="{{_rel}}styles/singulink.js"></script>
<script type="text/javascript" src="{{_rel}}styles/main.js"></script>

View File

@ -1,9 +0,0 @@
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
<div id="search-results" style="display: none;">
<h1 class="search-list">{{__global.searchResults}} <span></span></h1>
<div class="sr-items">
<p><i class="bi bi-hourglass-split index-loading"></i></p>
</div>
<ul id="pagination" data-first={{__global.pageFirst}} data-prev={{__global.pagePrev}} data-next={{__global.pageNext}} data-last={{__global.pageLast}}></ul>
</div>

View File

@ -1,5 +0,0 @@
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
<div id="sidetoggle">
<div id="sidetoc"></div>
</div>

View File

@ -1,114 +0,0 @@
/* Theme Configuration Options */
:root
{
/* General */
--base-font-size: 16px;
--smalldevice-base-font-size: 14px; /* Base font size for devices < 1024px */
--main-bg-color: #1f1f23;
--footer-bg-color: rgba(0,0,0,.4);
--separator-color: #42474f;
--table-strip-bg-color: #151515;
--table-header-bg-color: black;
--table-header-color: hsla(0,0%,100%,.8);
--table-header-border-color: #040405;
/* Text */
--appname-color: white;
--h1-color: white;
--h2-color: #f2f2f2;
--h3-color: #e3e3e3;
--h4-color: #ffffff;
--h5-color: #e0e0e0;
--text-color: #e1e1e1;
--link-color: #00b0f4;
--link-hover-color: #2ec4ff;
/* Mobile Topbar */
--topbar-bg-color: #18191c;
/* Button */
--button-color: #747f8d;
/* Sidebar */
--sidebar-width: 400px;
--sidebar-bg-color: #292B30;
--search-color: #bdbdbd;
--search-bg-color: #1b1e21;
--search-searchicon-color: #e3e3e3;
--search-border-color: black;
--sidebar-item-color: white;
--sidebar-active-item-color: #00b0f4;
--sidebar-level1-item-bg-color: #222429;
--sidebar-level1-item-hover-bg-color: #1D1F22;
--toc-filter-color: #bdbdbd;
--toc-filter-bg-color: #1b1e21;
--toc-filter-filtericon-color: #e3e3e3;
--toc-filter-clearicon-color: #e68585;
--toc-filter-border-color: black;
/* Scrollbars */
--scrollbar-bg-color: transparent;
--scrollbar-thumb-bg-color: rgba(0,0,0,.4);
--scrollbar-thumb-border-color: transparent;
/* Alerts and Blocks */
--alert-info-border-color: rgba(114,137,218,.5);
--alert-info-bg-color: rgba(114,137,218,.1);
--alert-warning-border-color: rgba(250,166,26,.5);
--alert-warning-bg-color: rgba(250,166,26,.1);
--alert-danger-border-color: rgba(240,71,71,.5);
--alert-danger-bg-color: rgba(240,71,71,.1);
--alert-tip-border-color: rgba(255,255,255,.5);
--alert-tip-bg-color: rgba(255,255,255,.1);
--blockquote-border-color: rgba(255,255,255,.5);
--blockquote-bg-color: rgba(255,255,255,.1);
--breadcrumb-bg-color: #2f3136;
/* Inline Code */
--ref-bg-color: black;
--ref-color: #89d4f1;
/* Code Blocks */
--code-bg-color: #151515;
--code-color: #d6deeb;
--code-keyword-color: #569cd6;
--code-comment-color: #57a64a;
--code-macro-color: #beb7ff;
--code-string-color: #d69d85;
--code-string-escape-color: #ffd68f;
--code-field-color: #c8c8c8;
--code-function-color: #dcdcaa;
--code-control-color: #d8a0df;
--code-class-color: #4ec9b0;
--code-number-color: #b5cea8;
--code-params-color: #9a9a9a;
--code-breakpoint-color: #8c2f2f;
}
/* Code Block Overrides */
pre, legend {
--scrollbar-thumb-bg-color: #333;
}

View File

@ -1,681 +0,0 @@
/* Discord Style */
::-webkit-scrollbar {
width: 10px;
}
::-webkit-scrollbar-track {
background: var(--scrollbar-bg-color);
}
::-webkit-scrollbar-thumb {
background: var(--scrollbar-thumb-bg-color);
border-color: var(--scrollbar-thumb-border-color);
border-radius: 5px;
}
::marker {
unicode-bidi: isolate;
font-variant-numeric: tabular-nums;
text-transform: none;
text-indent: 0px !important;
text-align: start !important;
text-align-last: start !important;
}
*, :after, :before
{
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
html, body
{
padding: 0;
margin: 0;
font: 15px/150% 'Roboto', sans-serif;
overflow: hidden;
color: var(--text-color);
background-color: var(--main-bg-color);
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
img {
max-width: 100%;
}
ul > li, ol > li {
display: list-item;
}
h1,h2,h3,h4,h5
{
color: var(--link-active-color);
position: relative;
}
h1, h2
{
margin-block-start: 2em;
}
h3
{
margin-block-start: 1em;
font-weight: 300;
font-size: 1.5em;
color: var(--h3-color);
margin-block-start: 3em;
}
h4
{
opacity: 1;
color: var(--h4-color);
font-size: large;
border-bottom: 2px solid var(--separator-color);
margin: 20px 0 0 0;
}
h5 {
margin-block-end: .8em;
margin-block-start: 1em;
font-size: .85em;
font-weight: 500;
color: var(--h5-color);
}
h6 {
font-size: .75em;
margin: 0;
}
p
{
font-weight: 400;
}
ul
{
position: relative;
}
ul, ol
{
padding-inline-start: 3em;
}
ul.level1
{
list-style-type: none;
padding-inline-start: 0;
}
ul.level2, ul.level3
{
padding-inline-start: 1em;
list-style-type: none;
font-size: .9em;
}
a
{
color: var(--link-color);
text-decoration: none;
transition: color .25s;
}
a:focus, a:hover
{
color: var(--link-hover-color);
text-decoration: underline;
}
a.anchorjs-link:hover {
text-decoration: none;
}
a.active, a:active
{
color: var(--link-active-color);
}
.body-content
{
display: flex;
flex-direction: row;
height: 100%;
overflow-x: hidden;
overflow-y: hidden;
}
.page-title
{
margin-block-start: 0;
}
nav
{
width: 300px;
transition: left .5s ease-out;
position: fixed;
left: -350px;
top: 40px;
bottom: 0;
background-color: var(--sidebar-bg-color);
overflow-y: auto;
display: flex;
flex-direction: column;
z-index: 1000;
}
h1:first-child
{
margin-block-start: 1.1em;
margin-top: 1.1em;
}
.sidebar
{
padding: 32px 17px 32px 32px;
flex: 1;
}
.sidebar-item
{
font-size: 1em;
font-weight: 400;
display: block;
padding: 4px 16px;
color: var(--sidebar-item-color);
}
.sidebar-item.large, #navbar .sidebar-item
{
padding: 8px 16px;
}
a.sidebar-item:hover, a.sidebar-item:focus
{
color: var(--link-active-color);
text-decoration: none;
}
a.sidebar-item.active
{
color: var(--link-active-color);
}
ul.level1 > li > a.sidebar-item
{
background-color: transparent;
border-radius: 4px;
}
#toc ul.level1 > li > a.sidebar-item.active
{
background-color: var(--link-active-bg-color);
}
.sidebar-item-separator
{
height: 2px;
width: 100%;
background-color: var(--separator-color);
margin: 2em 0;
opacity: .8;
}
span.sidebar-item
{
font-weight: 700;
text-transform: uppercase;
font-size: .8em;
color: var(--text-color);
margin-block-start: 1.25em;
}
.main-panel
{
background-color: var(--main-bg-color);
flex: 1;
overflow-y: auto;
padding: 20px 40px;
}
.top-navbar
{
display: flex;
flex-direction: row;
align-items: center;
padding: 0 40px;
height: 40px;
background-color: var(--topbar-bg-color);
}
.burger-icon
{
margin-right: 1em;
color: var(--button-color);
}
.burger-icon:hover, .burger-icon:focus
{
color: var(--link-active-color);
}
.burger-icon.active, .burger-icon:active
{
color: var(--link-active-color);
}
.brand
{
display: flex;
align-items: center;
justify-content: start;
}
.logomark
{
height: 28px;
}
.brand-title
{
padding: 0 .5em;
font-size: .9em;
color: var(--link-active-color);
}
.footer
{
background-color: var(--footer-bg-color);
padding: 20px;
margin: 0 20px 20px 20px;
border-radius: 8px;
color: var(--link-active-color);
}
.footer > h4
{
margin-block-start: 0;
}
.blackout
{
display: block;
visibility: hidden;
position: absolute;
z-index: 100;
top: 40px;
bottom: 0;
left: 0;
right: 0;
background-color: var(--footer-bg-color);
}
@keyframes showThat {
0% { opacity: 0; visibility: hidden; }
1% { opacity: 0; visibility: visible; }
100% { opacity: 1; visibility: visible;}
}
@keyframes hideThat {
0% { opacity: 1; visibility: visible; }
99% { opacity: 0; visibility: visible; }
100% { opacity: 0; visibility: hidden;}
}
.showThat
{
animation: showThat .5s forwards;
}
.hideThat
{
animation: hideThat .5s forwards;
}
@media (min-width: 1024px)
{
nav
{
position: relative;
left: 0!important;
top: 0;
bottom: 0;
}
.top-navbar
{
display: none;
}
.blackout
{
display: none;
}
}
/* Table */
.table-responsive
{
overflow-x: auto;
margin-bottom: 64px;
}
table
{
background-color: var(--code-bg-color);
border-collapse: collapse;
width: 100%;
table-layout: auto;
}
table.table-striped tbody tr:nth-child(2n)
{
background-color: var(--table-strip-bg-color);
}
table thead
{
background: var(--table-header-bg-color);
}
table th
{
color: var(--table-header-color);
text-transform: uppercase;
font-size: 12px;
line-height: 15px;
border-bottom: 1px solid var(--table-header-border-color);
padding: 8px;
}
.table-condensed th {
text-align: left;
}
table td
{
padding: 8px;
font-weight: 300;
}
table td > p
{
margin: 0;
}
/* Alerts */
.alert {
border-radius: 4px;
padding: 8px;
margin: 25px 0;
}
.alert > h5
{
display: none;
margin: 0;
}
.alert > p
{
margin: 0;
font-weight: 300;
font-size: 13px;
}
.alert.alert-info
{
border: 2px solid var(--alert-info-border-color);
background: var(--alert-info-bg-color);
}
.alert.alert-warning
{
border: 2px solid var(--alert-warning-border-color);
background: var(--alert-warning-bg-color);
}
.alert.alert-danger
{
border: 2px solid var(--alert-danger-border-color);
background: var(--alert-danger-bg-color);
}
.TIP.alert.alert-info
{
border: 2px solid var(--alert-tip-border-color);
background: var(--alert-tip-bg-color);
}
blockquote {
margin: 8px 0;
border-left: 4px solid var(--blockquote-border-color);
padding: 8px;
background: var(--blockquote-bg-color);
border-radius: 4px;
}
blockquote > p {
margin: 0;
font-style: italic;
font-size: 13px;
}
/* Breadcrumb */
#breadcrumb
{
padding: 8px 16px;
background: var(--breadcrumb-bg-color);
border-radius: 4px;
margin-bottom: 30px;
}
#breadcrumb:empty
{
display: none;
}
ul.breadcrumb
{
display: flex;
flex-direction: row;
margin: 0;
}
ul.breadcrumb > li {
margin-right: 6px;
}
ul.breadcrumb > li::before
{
content: "/";
margin-right: 5px;
}
ul.breadcrumb > li:first-child::before
{
content: "";
margin: 0;
}
/* Code */
legend, pre
{
display: block;
background-color: var(--code-bg-color);
padding: 16px;
border-radius: 4px;
}
code
{
background-color: var(--code-bg-color);
padding: 2px 4px;
border-radius: 4px;
}
.hljs
{
background: transparent;
}
/* DocFX related */
.small {
font-size: .9em;
}
.pull-right
{
float: right;
}
.hide
{
display: none;
}
@media (max-width: 1023.98px)
{
.mobile-hide
{
display: none;
}
}
li
{
display: block;
position: relative;
}
.expand-stub
{
cursor: pointer;
position: absolute;
width: 20px;
height: 20px;
left: -10px;
}
ul.level1 > li > .expand-stub
{
display: none;
}
.toc .nav > li > .expand-stub::before, .toc .nav > li.active > .expand-stub::before
{
content: " ";
position: absolute;
transform: rotate(-90deg);
width: 10px;
height: 10px;
top: 5px;
left: 5px;
background-repeat: no-repeat;
background: url(down-arrow.svg);
}
.toc .nav > li.active > .expand-stub::before, .toc .nav > li.in > .expand-stub::before, .toc .nav > li.in.active > .expand-stub::before, .toc .nav > li.filtered > .expand-stub::before
{
transform: none;
}
li > ul
{
display: none;
}
li.in > ul
{
display: block;
}
ul.level2 > li > a.sidebar-item,
ul.level3 > li > a.sidebar-item
{
font-weight: 500;
font-size: .95em;
padding: 0;
margin: 2px 16px;
}
ul.level2 > li > a.sidebar-item
{
color: var(--sidebar-item-2nd-color);
}
ul.level3 > li > a.sidebar-item
{
color: var(--sidebar-item-3rd-color);
}
ul.level2 > li > a.sidebar-item:hover,
ul.level2 > li > a.sidebar-item:focus,
ul.level3 > li > a.sidebar-item:hover,
ul.level3 > li > a.sidebar-item:focus
{
color: var(--link-active-color);
text-decoration: underline;
}
ul.level2 > li > a.sidebar-item.active,
ul.level3 > li > a.sidebar-item.active
{
color: var(--link-active-color);
}
.inheritance .level0:before,
.inheritance .level1:before,
.inheritance .level2:before,
.inheritance .level3:before,
.inheritance .level4:before,
.inheritance .level5:before {
content: '↳';
margin-right: 5px;
}
.inheritance .level0 {
margin-left: 0em;
}
.inheritance .level1 {
margin-left: 1em;
}
.inheritance .level2 {
margin-left: 2em;
}
.inheritance .level3 {
margin-left: 3em;
}
.inheritance .level4 {
margin-left: 4em;
}
.inheritance .level5 {
margin-left: 5em;
}

View File

@ -1,44 +0,0 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 491.996 491.996" style="enable-background:new 0 0 491.996 491.996;" xml:space="preserve">
<g>
<g color="white">
<path d="M484.132,124.986l-16.116-16.228c-5.072-5.068-11.82-7.86-19.032-7.86c-7.208,0-13.964,2.792-19.036,7.86l-183.84,183.848
L62.056,108.554c-5.064-5.068-11.82-7.856-19.028-7.856s-13.968,2.788-19.036,7.856l-16.12,16.128
c-10.496,10.488-10.496,27.572,0,38.06l219.136,219.924c5.064,5.064,11.812,8.632,19.084,8.632h0.084
c7.212,0,13.96-3.572,19.024-8.632l218.932-219.328c5.072-5.064,7.856-12.016,7.864-19.224
C491.996,136.902,489.204,130.046,484.132,124.986z" fill="currentcolor"/>
</g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -1,317 +0,0 @@
/*!
* jQuery pagination plugin v1.4.1
* http://esimakin.github.io/twbs-pagination/
*
* Copyright 2014-2016, Eugene Simakin
* Released under Apache 2.0 license
* http://apache.org/licenses/LICENSE-2.0.html
*/
(function ($, window, document, undefined) {
'use strict';
var old = $.fn.twbsPagination;
// PROTOTYPE AND CONSTRUCTOR
var TwbsPagination = function (element, options) {
this.$element = $(element);
this.options = $.extend({}, $.fn.twbsPagination.defaults, options);
if (this.options.startPage < 1 || this.options.startPage > this.options.totalPages) {
throw new Error('Start page option is incorrect');
}
this.options.totalPages = parseInt(this.options.totalPages);
if (isNaN(this.options.totalPages)) {
throw new Error('Total pages option is not correct!');
}
this.options.visiblePages = parseInt(this.options.visiblePages);
if (isNaN(this.options.visiblePages)) {
throw new Error('Visible pages option is not correct!');
}
if (this.options.onPageClick instanceof Function) {
this.$element.first().on('page', this.options.onPageClick);
}
// hide if only one page exists
if (this.options.hideOnlyOnePage && this.options.totalPages == 1) {
this.$element.trigger('page', 1);
return this;
}
if (this.options.totalPages < this.options.visiblePages) {
this.options.visiblePages = this.options.totalPages;
}
if (this.options.href) {
this.options.startPage = this.getPageFromQueryString();
if (!this.options.startPage) {
this.options.startPage = 1;
}
}
var tagName = (typeof this.$element.prop === 'function') ?
this.$element.prop('tagName') : this.$element.attr('tagName');
if (tagName === 'UL') {
this.$listContainer = this.$element;
} else {
this.$listContainer = $('<ul></ul>');
}
this.$listContainer.addClass(this.options.paginationClass);
if (tagName !== 'UL') {
this.$element.append(this.$listContainer);
}
if (this.options.initiateStartPageClick) {
this.show(this.options.startPage);
} else {
this.render(this.getPages(this.options.startPage));
this.setupEvents();
}
return this;
};
TwbsPagination.prototype = {
constructor: TwbsPagination,
destroy: function () {
this.$element.empty();
this.$element.removeData('twbs-pagination');
this.$element.off('page');
return this;
},
show: function (page) {
if (page < 1 || page > this.options.totalPages) {
throw new Error('Page is incorrect.');
}
this.currentPage = page;
this.render(this.getPages(page));
this.setupEvents();
this.$element.trigger('page', page);
return this;
},
buildListItems: function (pages) {
var listItems = [];
if (this.options.first) {
listItems.push(this.buildItem('first', 1));
}
if (this.options.prev) {
var prev = pages.currentPage > 1 ? pages.currentPage - 1 : this.options.loop ? this.options.totalPages : 1;
listItems.push(this.buildItem('prev', prev));
}
for (var i = 0; i < pages.numeric.length; i++) {
listItems.push(this.buildItem('page', pages.numeric[i]));
}
if (this.options.next) {
var next = pages.currentPage < this.options.totalPages ? pages.currentPage + 1 : this.options.loop ? 1 : this.options.totalPages;
listItems.push(this.buildItem('next', next));
}
if (this.options.last) {
listItems.push(this.buildItem('last', this.options.totalPages));
}
return listItems;
},
buildItem: function (type, page) {
var $itemContainer = $('<li></li>'),
$itemContent = $('<a></a>'),
itemText = this.options[type] ? this.makeText(this.options[type], page) : page;
$itemContainer.addClass(this.options[type + 'Class']);
$itemContainer.data('page', page);
$itemContainer.data('page-type', type);
$itemContainer.append($itemContent.attr('href', this.makeHref(page)).addClass(this.options.anchorClass).html(itemText));
return $itemContainer;
},
getPages: function (currentPage) {
var pages = [];
var half = Math.floor(this.options.visiblePages / 2);
var start = currentPage - half + 1 - this.options.visiblePages % 2;
var end = currentPage + half;
// handle boundary case
if (start <= 0) {
start = 1;
end = this.options.visiblePages;
}
if (end > this.options.totalPages) {
start = this.options.totalPages - this.options.visiblePages + 1;
end = this.options.totalPages;
}
var itPage = start;
while (itPage <= end) {
pages.push(itPage);
itPage++;
}
return {"currentPage": currentPage, "numeric": pages};
},
render: function (pages) {
var _this = this;
this.$listContainer.children().remove();
var items = this.buildListItems(pages);
jQuery.each(items, function(key, item){
_this.$listContainer.append(item);
});
this.$listContainer.children().each(function () {
var $this = $(this),
pageType = $this.data('page-type');
switch (pageType) {
case 'page':
if ($this.data('page') === pages.currentPage) {
$this.addClass(_this.options.activeClass);
}
break;
case 'first':
$this.toggleClass(_this.options.disabledClass, pages.currentPage === 1);
break;
case 'last':
$this.toggleClass(_this.options.disabledClass, pages.currentPage === _this.options.totalPages);
break;
case 'prev':
$this.toggleClass(_this.options.disabledClass, !_this.options.loop && pages.currentPage === 1);
break;
case 'next':
$this.toggleClass(_this.options.disabledClass,
!_this.options.loop && pages.currentPage === _this.options.totalPages);
break;
default:
break;
}
});
},
setupEvents: function () {
var _this = this;
this.$listContainer.off('click').on('click', 'li', function (evt) {
var $this = $(this);
if ($this.hasClass(_this.options.disabledClass) || $this.hasClass(_this.options.activeClass)) {
return false;
}
// Prevent click event if href is not set.
!_this.options.href && evt.preventDefault();
_this.show(parseInt($this.data('page')));
});
},
makeHref: function (page) {
return this.options.href ? this.generateQueryString(page) : "#";
},
makeText: function (text, page) {
return text.replace(this.options.pageVariable, page)
.replace(this.options.totalPagesVariable, this.options.totalPages)
},
getPageFromQueryString: function (searchStr) {
var search = this.getSearchString(searchStr),
regex = new RegExp(this.options.pageVariable + '(=([^&#]*)|&|#|$)'),
page = regex.exec(search);
if (!page || !page[2]) {
return null;
}
page = decodeURIComponent(page[2]);
page = parseInt(page);
if (isNaN(page)) {
return null;
}
return page;
},
generateQueryString: function (pageNumber, searchStr) {
var search = this.getSearchString(searchStr),
regex = new RegExp(this.options.pageVariable + '=*[^&#]*');
if (!search) return '';
return '?' + search.replace(regex, this.options.pageVariable + '=' + pageNumber);
},
getSearchString: function (searchStr) {
var search = searchStr || window.location.search;
if (search === '') {
return null;
}
if (search.indexOf('?') === 0) search = search.substr(1);
return search;
}
};
// PLUGIN DEFINITION
$.fn.twbsPagination = function (option) {
var args = Array.prototype.slice.call(arguments, 1);
var methodReturn;
var $this = $(this);
var data = $this.data('twbs-pagination');
var options = typeof option === 'object' ? option : {};
if (!data) $this.data('twbs-pagination', (data = new TwbsPagination(this, options) ));
if (typeof option === 'string') methodReturn = data[ option ].apply(data, args);
return ( methodReturn === undefined ) ? $this : methodReturn;
};
$.fn.twbsPagination.defaults = {
totalPages: 1,
startPage: 1,
visiblePages: 5,
initiateStartPageClick: true,
hideOnlyOnePage: false,
href: false,
pageVariable: '{{page}}',
totalPagesVariable: '{{total_pages}}',
page: null,
first: 'First',
prev: 'Previous',
next: 'Next',
last: 'Last',
loop: false,
onPageClick: null,
paginationClass: 'pagination',
nextClass: 'page-item next',
prevClass: 'page-item prev',
lastClass: 'page-item last',
firstClass: 'page-item first',
pageClass: 'page-item',
activeClass: 'active',
disabledClass: 'disabled',
anchorClass: 'page-link'
};
$.fn.twbsPagination.Constructor = TwbsPagination;
$.fn.twbsPagination.noConflict = function () {
$.fn.twbsPagination = old;
return this;
};
$.fn.twbsPagination.version = "1.4.1";
})(window.jQuery, window, document);

View File

@ -1,471 +0,0 @@
body {
font-size: var(--base-font-size);
}
@media (max-width: 1024px) {
body {
font-size: var(--smalldevice-base-font-size);
}
}
/* Headings */
h1, h2, h3, h4, h5 {
line-height: initial;
}
h1, h1:first-child {
font-size: 2.25em;
letter-spacing: 0.5px;
color: var(--h1-color);
margin-block-start: 1em;
margin-block-end: -0.05em;
}
.article h1 {
margin-block-end: -0.2em;
}
h2 {
font-size: 2.1em;
color: var(--h2-color);
}
.article h2 {
margin-block-start: 1.3em;
padding-bottom: 6px;
border-bottom: 1px solid var(--separator-color);
}
h3 {
font-size: 1.95em;
font-weight: 500;
margin-block-start: 1.7em;
}
.article h3 {
font-size: 1.85em;
font-weight: 500;
margin-block-start: 1.2em;
margin-block-end: 0.9em;
}
h4 {
font-size: 1.8em;
font-weight: 400;
margin-block-start: 2em;
padding-bottom: 10px;
}
.article h4 {
font-size: 1.5em;
font-weight: 300;
margin-block-start: 1em;
margin-block-end: 1em;
padding-bottom: 0;
border-bottom: none;
}
h5 {
font-size: 1.1em;
}
.article h5 {
font-size: 1.13em;
font-weight: 400;
text-decoration: underline;
margin-block-start: 1.5em;
margin-block-end: 1.0em;
}
a.brand:hover
{
text-decoration: none;
}
a.brand .brand-title {
font-size: 1.4em;
font-weight: 500;
letter-spacing: 0.5px;
color: var(--appname-color);
margin-top: 1px;
padding: 0 0 0 0.4em;
}
@media (min-width: 1024px) {
a.brand .brand-title {
font-size: 1.55em;
}
}
a.brand .logomark {
height: 35px;
}
/* Top bar */
.top-navbar {
height: 60px;
padding: 0 0 0 10px;
}
.burger-icon {
margin-right: 10px;
}
/* Side Bar */
.sidebar {
padding: 25px 17px 32px 17px;
}
.blackout {
top: 60px;
}
@media (max-width: 1023.98px) {
.navbar-nav {
margin-top: 0;
}
}
nav {
width: 94%;
max-width: var(--sidebar-width);
left: calc(var(--sidebar-width) * -1);
}
@media (max-width: 1023.98px) {
nav {
top: 60px;
}
}
nav ul {
list-style-type: none;
}
nav .nav a, nav .nav a:hover {
text-decoration: none;
cursor: pointer;
display: block;
}
nav a.sidebar-item {
padding: 4px 0 4px 10px;
cursor: pointer;
}
nav a:focus, nav a.sidebar-item:hover, nav a.sidebar-item:focus {
text-decoration: underline;
}
nav a, nav a:hover, nav a:focus {
color: var(--sidebar-item-color) !important;
}
nav a.active, nav a.active:hover, nav a.active:focus {
color: var(--sidebar-active-item-color) !important;
}
.sidebar-item-separator {
margin: 20px 0;
}
#toc ul li a {
padding: 0 0 0 10px;
}
.search {
background: var(--search-bg-color);
border: 1px solid var(--search-border-color);
border-radius: 5px;
position: relative;
margin-block-start: 25px;
}
@media (max-width: 1023.98px) {
.search {
margin-block-start: 0;
margin-block-end: 15px;
}
}
.search > input {
font-size: 0.95em;
color: var(--search-color);
border: 0;
background: none;
padding: 11px 30px 10px 37px;
width: 100%;
}
.search > input:focus {
outline: 0;
}
.search > .search-icon {
font-size: 1.2em;
color: var(--search-searchicon-color);
position: absolute;
top: 9px;
left: 9px;
}
.toc-filter {
background: var(--toc-filter-bg-color);
border: 1px solid var(--toc-filter-border-color);
border-radius: 5px;
position: relative;
}
.toc-filter > input {
font-size: 0.95em;
color: var(--toc-filter-color);
border: 0;
background: none;
padding: 11px 30px 10px 37px;
width: 100%;
}
.toc-filter > input:focus {
outline: 0;
}
.toc-filter > .filter-icon {
font-size: 1.2em;
color: var(--toc-filter-filtericon-color);
position: absolute;
top: 9px;
left: 9px;
}
.toc-filter > .clear-icon {
color: var(--toc-filter-clearicon-color);
position: absolute;
top: 9px;
right: 9px;
cursor: pointer;
}
.toc .nav > li > .expand-stub::before, .toc .nav > li.active > .expand-stub::before
{
width: 8px;
height: 8px;
top: 6px;
left: 6px;
}
#toc ul.level2
{
margin-bottom: 20px;
}
#toc ul.level1 > li > a {
font-weight: 500;
margin-bottom: 10px;
padding: 5px 10px;
}
#toc ul.level1 > li > a, #toc ul.level1 > li > a.active {
background-color: var(--sidebar-level1-item-bg-color) !important;
border-radius: 2px;
}
#toc ul.level1 > li > a:hover, #toc ul.level1 > li > a.active:hover,
#toc ul.level1 > li > a:focus, #toc ul.level1 > li > a.active:focus {
background-color: var(--sidebar-level1-item-hover-bg-color) !important;
text-decoration: none;
}
ul.level2 {
padding-inline-start: 0.7em;
}
ul.level2 .expand-stub {
top: 1px;
}
ul.level2 > li > a, ul.level2 > li > a.sidebar-item {
font-weight: 400;
color: var(--sidebar-item-color);
margin: 4px 0 4px;
}
ul.level3 {
padding-inline-start: 1em;
}
ul.level3 > li > a, ul.level3 > li > a.sidebar-item {
font-size: 1.05em;
color: var(--sidebar-item-color);
margin: 4px 0;
}
ul.level4 {
padding-inline-start: 0;
margin-bottom: 12px;
}
ul.level4 > li > a, ul.level4 > li > a.sidebar-item {
font-size: 1.05em;
color: var(--sidebar-item-color);
margin: 5px 0 5px 10px;
}
/* Breadcrumbs */
.subnav.navbar {
margin: 0 -15px;
}
#breadcrumb {
overflow: scroll;
margin-bottom: 0;
}
#breadcrumb::-webkit-scrollbar {
display: none;
}
#breadcrumb a {
white-space: nowrap;
}
#breadcrumb wbr {
display: none;
}
/* Search Results */
#search-results h1 {
margin-block-start: 0.5em;
}
#search-results .item-title {
font-size: 1.3em;
margin-top: 1.5em;
}
#search-results .item-href {
font-size: 0.85em;
}
#search-results .item-brief {
margin-top: 0.7em;
}
#search-results ul.pagination {
text-align: center;
padding: 10px 0 0 0;
margin-block-start: 40px;
border-top: 1px solid var(--separator-color);
}
#search-results ul.pagination > li {
display: inline-block;
margin: 0 10px;
}
#search-results ul.pagination > li.disabled a, #search-results ul.pagination > li.disabled a:hover {
color: var(--text-color);
cursor: txt;
text-decoration: none;
}
/* Content */
.main-panel {
margin-bottom: 60px;
padding: 20px;
}
@media (min-width: 1024px) {
.main-panel {
margin-bottom: 0;
padding: 20px 40px;
}
}
.pull-right {
margin-top: 70px;
/* Fix unclickable links */
position: relative;
z-index: 1;
}
.divider {
margin-left: 4px;
}
article ul li, article ol li {
margin-bottom: 10px;
}
legend, pre {
padding: 6px;
}
.hljs {
color: var(--code-color);
}
.hljs::-webkit-scrollbar {
height: 6px;
}
.hljs-keyword, .hljs-title, .hljs-built_in {
font-style: normal;
}
p .xref, code {
background-color: var(--ref-bg-color);
color: var(--ref-color);
padding: 2px 3px;
font-family: monospace;
font-size: 0.95em;
border-radius: 6px;
}
span.parametername {
font-family: monospace;
}
.table {
width: auto;
}
.table-responsive {
margin-bottom: 0;
}
table th {
font-size: 14px;
padding: 9px 10px;
}
table td p {
font-weight: 300;
}
table td {
padding: 6px 10px;
}
.footer {
text-align: center;
color: var(--text-color);
padding: 10px;
}
.footer a:hover, .footer a:focus {
text-decoration: underline;
}
.copyright-footer {
font-size: 0.85em;
font-weight: bold;
text-align: center;
margin-block-start: 30px;
}

View File

@ -1,38 +0,0 @@
// Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.
function toggleMenu() {
var sidebar = document.getElementById("sidebar");
var blackout = document.getElementById("blackout");
if (sidebar.style.left === "0px")
{
sidebar.style.left = "-" + sidebar.offsetWidth + "px";
blackout.classList.remove("showThat");
blackout.classList.add("hideThat");
}
else
{
sidebar.style.left = "0px";
blackout.classList.remove("hideThat");
blackout.classList.add("showThat");
}
}
$(function() {
$('table').each(function(a, tbl) {
var currentTableRows = $(tbl).find('tbody tr').length;
$(tbl).find('th').each(function(i) {
var remove = 0;
var currentTable = $(this).parents('table');
var tds = currentTable.find('tr td:nth-child(' + (i + 1) + ')');
tds.each(function(j) { if ($(this).text().trim() === '') remove++; });
if (remove == currentTableRows) {
$(this).hide();
tds.hide();
}
});
});
});

View File

@ -1 +0,0 @@
/*! url - v1.8.6 - 2013-11-22 */window.url=function(){function a(a){return!isNaN(parseFloat(a))&&isFinite(a)}return function(b,c){var d=c||window.location.toString();if(!b)return d;b=b.toString(),"//"===d.substring(0,2)?d="http:"+d:1===d.split("://").length&&(d="http://"+d),c=d.split("/");var e={auth:""},f=c[2].split("@");1===f.length?f=f[0].split(":"):(e.auth=f[0],f=f[1].split(":")),e.protocol=c[0],e.hostname=f[0],e.port=f[1]||("https"===e.protocol.split(":")[0].toLowerCase()?"443":"80"),e.pathname=(c.length>3?"/":"")+c.slice(3,c.length).join("/").split("?")[0].split("#")[0];var g=e.pathname;"/"===g.charAt(g.length-1)&&(g=g.substring(0,g.length-1));var h=e.hostname,i=h.split("."),j=g.split("/");if("hostname"===b)return h;if("domain"===b)return/^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/.test(h)?h:i.slice(-2).join(".");if("sub"===b)return i.slice(0,i.length-2).join(".");if("port"===b)return e.port;if("protocol"===b)return e.protocol.split(":")[0];if("auth"===b)return e.auth;if("user"===b)return e.auth.split(":")[0];if("pass"===b)return e.auth.split(":")[1]||"";if("path"===b)return e.pathname;if("."===b.charAt(0)){if(b=b.substring(1),a(b))return b=parseInt(b,10),i[0>b?i.length+b:b-1]||""}else{if(a(b))return b=parseInt(b,10),j[0>b?j.length+b:b]||"";if("file"===b)return j.slice(-1)[0];if("filename"===b)return j.slice(-1)[0].split(".")[0];if("fileext"===b)return j.slice(-1)[0].split(".")[1]||"";if("?"===b.charAt(0)||"#"===b.charAt(0)){var k=d,l=null;if("?"===b.charAt(0)?k=(k.split("?")[1]||"").split("#")[0]:"#"===b.charAt(0)&&(k=k.split("#")[1]||""),!b.charAt(1))return k;b=b.substring(1),k=k.split("&");for(var m=0,n=k.length;n>m;m++)if(l=k[m].split("="),l[0]===b)return l[1]||"";return null}}return""}}(),"undefined"!=typeof jQuery&&jQuery.extend({url:function(a,b){return window.url(a,b)}});

View File

@ -1,22 +0,0 @@
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
<div id="sidetoggle">
<div>
{{^_disableSideFilter}}
<div class="sidefilter">
<form class="toc-filter">
<i class="bi bi-funnel-fill filter-icon"></i>
<i id="toc_filter_clear" class="bi bi-x-lg clear-icon"></i>
<input type="text" id="toc_filter_input" placeholder="{{__global.tocFilter}}" autocomplete="off" onkeypress="if(event.keyCode==13) {return false;}">
</form>
</div>
{{/_disableSideFilter}}
<div class="sidetoc">
<div class="toc" id="toc">
{{^leaf}}
{{>partials/li}}
{{/leaf}}
</div>
</div>
</div>
</div>

View File

@ -1,2 +1,2 @@
- name: API Documentation
href: api/
- name: Plugin API
href: api/

View File

@ -0,0 +1 @@
# Getting Started

View File

@ -0,0 +1 @@
# Introduction

4
docfx/docs/toc.yml Normal file
View File

@ -0,0 +1,4 @@
- name: Introduction
href: introduction.md
- name: Getting Started
href: getting-started.md

View File

@ -1,10 +0,0 @@
To generate the [Artemis API docs](https://artemis-rgb.com/docs/) we use [DocFX](https://dotnet.github.io/docfx/).
To build locally run the following commands from this folder.
#### Want to build? Follow these instructions (Windows)
1. Ensure you can build the Artemis solution as per the [main build instructions](https://github.com/Artemis-RGB/Artemis#want-to-build-follow-these-instructions)
2. Install DocFX (with Chocolatey: `choco install docfx`)
3. Open PowerShell in `<repo>\docfx` (the same folder as this readme file)
4. Run `docfx .\docfx_project\docfx.json` to build static files
Run `docfx .\docfx_project\docfx.json --serve` to serve locally

View File

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<PreserveCompilationContext>false</PreserveCompilationContext>
<ShouldIncludeNativeSkiaSharp>false</ShouldIncludeNativeSkiaSharp>
<AssemblyTitle>Artemis.Core</AssemblyTitle>
@ -36,20 +36,20 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="DryIoc.dll" Version="5.4.3" />
<PackageReference Include="EmbedIO" Version="3.5.2" />
<PackageReference Include="HidSharp" Version="2.1.0" />
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
<PackageReference Include="JetBrains.Annotations" Version="2023.3.0" />
<PackageReference Include="McMaster.NETCore.Plugins" Version="1.4.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="RGB.NET.Core" Version="2.0.4-prerelease.1" />
<PackageReference Include="RGB.NET.Layout" Version="2.0.4-prerelease.1" />
<PackageReference Include="RGB.NET.Presets" Version="2.0.4-prerelease.1" />
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.1" />
<PackageReference Include="Serilog.Sinks.Debug" Version="2.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="SkiaSharp" Version="2.88.7" />
<PackageReference Include="DryIoc.dll" />
<PackageReference Include="EmbedIO" />
<PackageReference Include="HidSharp" />
<PackageReference Include="Humanizer.Core" />
<PackageReference Include="JetBrains.Annotations" />
<PackageReference Include="McMaster.NETCore.Plugins" />
<PackageReference Include="Newtonsoft.Json" />
<PackageReference Include="RGB.NET.Core" />
<PackageReference Include="RGB.NET.Layout" />
<PackageReference Include="RGB.NET.Presets" />
<PackageReference Include="Serilog.Sinks.Console" />
<PackageReference Include="Serilog.Sinks.Debug" />
<PackageReference Include="Serilog.Sinks.File" />
<PackageReference Include="SkiaSharp" />
</ItemGroup>
<ItemGroup>

View File

@ -5,7 +5,7 @@ using Artemis.Core.DryIoc.Factories;
using Artemis.Core.Providers;
using Artemis.Core.Services;
using Artemis.Storage;
using Artemis.Storage.Migrations.Interfaces;
using Artemis.Storage.Migrations;
using Artemis.Storage.Repositories.Interfaces;
using DryIoc;
@ -36,6 +36,7 @@ public static class ContainerExtensions
// Bind migrations
container.RegisterMany(storageAssembly, type => type.IsAssignableTo<IStorageMigration>(), Reuse.Singleton, nonPublicServiceTypes: true);
container.RegisterMany(storageAssembly, type => type.IsAssignableTo<IProfileMigration>(), Reuse.Singleton, nonPublicServiceTypes: true);
container.RegisterMany(coreAssembly, type => type.IsAssignableTo<ILayoutProvider>(), Reuse.Singleton);
container.Register<IPluginSettingsFactory, PluginSettingsFactory>(Reuse.Singleton);

View File

@ -248,8 +248,8 @@ public sealed class Layer : RenderProfileElement
typeof(PropertyGroupDescriptionAttribute)
)!;
LayerEntity.GeneralPropertyGroup ??= new PropertyGroupEntity {Identifier = generalAttribute.Identifier};
LayerEntity.TransformPropertyGroup ??= new PropertyGroupEntity {Identifier = transformAttribute.Identifier};
LayerEntity.GeneralPropertyGroup ??= new PropertyGroupEntity {Identifier = generalAttribute.Identifier!};
LayerEntity.TransformPropertyGroup ??= new PropertyGroupEntity {Identifier = transformAttribute.Identifier!};
General.Initialize(this, null, generalAttribute, LayerEntity.GeneralPropertyGroup);
Transform.Initialize(this, null, transformAttribute, LayerEntity.TransformPropertyGroup);

View File

@ -240,7 +240,8 @@ public abstract class LayerPropertyGroup : IDisposable
foreach (LayerPropertyGroup layerPropertyGroup in LayerPropertyGroups)
{
layerPropertyGroup.ApplyToEntity();
PropertyGroupEntity.PropertyGroups.Add(layerPropertyGroup.PropertyGroupEntity);
if (layerPropertyGroup.PropertyGroupEntity != null)
PropertyGroupEntity.PropertyGroups.Add(layerPropertyGroup.PropertyGroupEntity);
}
}

View File

@ -46,7 +46,7 @@ public class ArtemisDevice : CorePropertyChanged
InputIdentifiers = new List<ArtemisDeviceInputIdentifier>();
InputMappings = new Dictionary<ArtemisLed, ArtemisLed>();
Categories = new HashSet<DeviceCategory>();
LayoutSelection = new LayoutSelection {Type = DefaultLayoutProvider.LayoutType};
LayoutSelection = new LayoutSelection {Type = DefaultLayoutProvider.LAYOUT_TYPE};
RgbDevice.ColorCorrections.Clear();
RgbDevice.ColorCorrections.Add(new ScaleColorCorrection(this));
@ -75,7 +75,7 @@ public class ArtemisDevice : CorePropertyChanged
InputIdentifiers = new List<ArtemisDeviceInputIdentifier>();
InputMappings = new Dictionary<ArtemisLed, ArtemisLed>();
Categories = new HashSet<DeviceCategory>();
LayoutSelection = new LayoutSelection {Type = DefaultLayoutProvider.LayoutType};
LayoutSelection = new LayoutSelection {Type = DefaultLayoutProvider.LAYOUT_TYPE};
foreach (DeviceInputIdentifierEntity identifierEntity in DeviceEntity.InputIdentifiers)
InputIdentifiers.Add(new ArtemisDeviceInputIdentifier(identifierEntity.InputProvider, identifierEntity.Identifier));
@ -155,6 +155,9 @@ public class ArtemisDevice : CorePropertyChanged
/// </summary>
public HashSet<DeviceCategory> Categories { get; }
/// <summary>
/// Gets the layout selection applied to this device
/// </summary>
public LayoutSelection LayoutSelection { get; }
/// <summary>

View File

@ -57,6 +57,9 @@ public class ArtemisLayout
/// </summary>
public LayoutCustomDeviceData LayoutCustomDeviceData { get; private set; } = null!;
/// <summary>
/// Gets a boolean indicating whether this layout is a default layout or not
/// </summary>
public bool IsDefaultLayout { get; private set; }
/// <summary>

View File

@ -65,7 +65,7 @@ public class LayerBrushDescriptor
BaseLayerBrush brush = (BaseLayerBrush) Provider.Plugin.Resolve(LayerBrushType);
brush.Layer = layer;
brush.Descriptor = this;
brush.LayerBrushEntity = entity ?? new LayerBrushEntity {ProviderId = Provider.Id, BrushType = LayerBrushType.FullName};
brush.LayerBrushEntity = entity ?? new LayerBrushEntity {ProviderId = Provider.Id, BrushType = LayerBrushType.FullName ?? throw new InvalidOperationException()};
brush.Initialize();
return brush;

View File

@ -231,7 +231,7 @@ public abstract class BaseLayerEffect : BreakableModel, IDisposable, IStorageMod
return;
LayerEffectEntity.ProviderId = Descriptor.Provider.Id;
LayerEffectEntity.EffectType = GetType().FullName;
LayerEffectEntity.EffectType = GetType().FullName ?? throw new InvalidOperationException();
BaseProperties?.ApplyToEntity();
LayerEffectEntity.PropertyGroup = BaseProperties?.PropertyGroupEntity;
}

View File

@ -0,0 +1,81 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Reflection;
using SkiaSharp;
namespace Artemis.Core.Nodes;
/// <summary>
/// Allows you to register one or more <see cref="INode" />s usable by node scripts.
/// </summary>
public abstract class NodeProvider : PluginFeature
{
private readonly List<NodeData> _nodeDescriptors;
/// <summary>
/// Creates a new instance of the <see cref="NodeProvider"/> class.
/// </summary>
public NodeProvider()
{
_nodeDescriptors = new List<NodeData>();
NodeDescriptors = new ReadOnlyCollection<NodeData>(_nodeDescriptors);
Disabled += OnDisabled;
}
/// <summary>
/// A read-only collection of all nodes added with <see cref="RegisterNodeType{T}" />
/// </summary>
public ReadOnlyCollection<NodeData> NodeDescriptors { get; set; }
/// <summary>
/// Adds a node descriptor for a given node, so that it appears in the UI.
/// <para>Note: You do not need to manually remove these on disable</para>
/// </summary>
/// <typeparam name="T">The type of the node you wish to register</typeparam>
protected void RegisterNodeType<T>() where T : INode
{
RegisterNodeType(typeof(T));
}
/// <summary>
/// Adds a node descriptor for a given node, so that it appears in the UI.
/// <para>Note: You do not need to manually remove these on disable</para>
/// </summary>
/// <param name="nodeType">The type of the node you wish to register</param>
protected void RegisterNodeType(Type nodeType)
{
if (!IsEnabled)
throw new ArtemisPluginFeatureException(this, "Can only add a node descriptor when the plugin is enabled");
if (nodeType == null)
throw new ArgumentNullException(nameof(nodeType));
if (!nodeType.IsAssignableTo(typeof(INode)))
throw new ArgumentException("Node has to be a base type of the Node-Type.", nameof(nodeType));
NodeAttribute? nodeAttribute = nodeType.GetCustomAttribute<NodeAttribute>();
string name = nodeAttribute?.Name ?? nodeType.Name;
string description = nodeAttribute?.Description ?? string.Empty;
string category = nodeAttribute?.Category ?? string.Empty;
string helpUrl = nodeAttribute?.HelpUrl ?? string.Empty;
NodeData nodeData = new(this, nodeType, name, description, category, helpUrl, nodeAttribute?.InputType, nodeAttribute?.OutputType);
_nodeDescriptors.Add(nodeData);
NodeTypeStore.Add(nodeData);
}
/// <summary>
/// Adds a color for lines of the provided type.
/// </summary>
/// <param name="color">The color to add.</param>
/// <typeparam name="T">The type to use the color for.</typeparam>
protected TypeColorRegistration RegisterTypeColor<T>(SKColor color)
{
return NodeTypeStore.AddColor(typeof(T), color, this);
}
private void OnDisabled(object? sender, EventArgs e)
{
// The store will clean up the registrations by itself, the plugin feature just needs to clear its own list
_nodeDescriptors.Clear();
}
}

View File

@ -41,7 +41,7 @@ public class PluginSettings
if (_settingEntities.ContainsKey(name))
return (PluginSetting<T>) _settingEntities[name];
// Try to find in database
PluginSettingEntity settingEntity = _pluginRepository.GetSettingByNameAndGuid(name, Plugin.Guid);
PluginSettingEntity? settingEntity = _pluginRepository.GetSettingByNameAndGuid(name, Plugin.Guid);
// If not found, create a new one
if (settingEntity == null)
{

View File

@ -1,8 +1,14 @@
namespace Artemis.Core.Providers;
/// <summary>
/// Represents a layout provider that loads a layout from a custom path.
/// </summary>
public class CustomPathLayoutProvider : ILayoutProvider
{
public static string LayoutType = "CustomPath";
/// <summary>
/// The layout type of this layout provider.
/// </summary>
public const string LAYOUT_TYPE = "CustomPath";
/// <inheritdoc />
public ArtemisLayout? GetDeviceLayout(ArtemisDevice device)
@ -21,7 +27,7 @@ public class CustomPathLayoutProvider : ILayoutProvider
/// <inheritdoc />
public bool IsMatch(ArtemisDevice device)
{
return device.LayoutSelection.Type == LayoutType;
return device.LayoutSelection.Type == LAYOUT_TYPE;
}
/// <summary>
@ -31,7 +37,7 @@ public class CustomPathLayoutProvider : ILayoutProvider
/// <param name="path">The path to the custom layout.</param>
public void ConfigureDevice(ArtemisDevice device, string? path)
{
device.LayoutSelection.Type = LayoutType;
device.LayoutSelection.Type = LAYOUT_TYPE;
device.LayoutSelection.Parameter = path;
}
}

View File

@ -1,8 +1,14 @@
namespace Artemis.Core.Providers;
/// <summary>
/// Represents a layout provider that loads a layout from the plugin and falls back to a default layout.
/// </summary>
public class DefaultLayoutProvider : ILayoutProvider
{
public static string LayoutType = "Default";
/// <summary>
/// The layout type of this layout provider.
/// </summary>
public const string LAYOUT_TYPE = "Default";
/// <inheritdoc />
public ArtemisLayout? GetDeviceLayout(ArtemisDevice device)
@ -26,7 +32,7 @@ public class DefaultLayoutProvider : ILayoutProvider
/// <inheritdoc />
public bool IsMatch(ArtemisDevice device)
{
return device.LayoutSelection.Type == LayoutType;
return device.LayoutSelection.Type == LAYOUT_TYPE;
}
/// <summary>
@ -35,7 +41,7 @@ public class DefaultLayoutProvider : ILayoutProvider
/// <param name="device">The device to apply the provider to.</param>
public void ConfigureDevice(ArtemisDevice device)
{
device.LayoutSelection.Type = LayoutType;
device.LayoutSelection.Type = LAYOUT_TYPE;
device.LayoutSelection.Parameter = null;
}
}

View File

@ -12,6 +12,17 @@ public interface ILayoutProvider
/// <returns>The resulting layout if one was available; otherwise <see langword="null" />.</returns>
ArtemisLayout? GetDeviceLayout(ArtemisDevice device);
/// <summary>
/// Applies the layout to the provided device.
/// </summary>
/// <param name="device">The device to apply to.</param>
/// <param name="layout">The layout to apply.</param>
void ApplyLayout(ArtemisDevice device, ArtemisLayout layout);
/// <summary>
/// Determines whether the provided device is configured to use this layout provider.
/// </summary>
/// <param name="device">The device to check.</param>
/// <returns>A value indicating whether the provided device is configured to use this layout provider.</returns>
bool IsMatch(ArtemisDevice device);
}

View File

@ -1,8 +1,14 @@
namespace Artemis.Core.Providers;
/// <summary>
/// Represents a layout provider that does not load a layout.
/// </summary>
public class NoneLayoutProvider : ILayoutProvider
{
public static string LayoutType = "None";
/// <summary>
/// The layout type of this layout provider.
/// </summary>
public const string LAYOUT_TYPE = "None";
/// <inheritdoc />
public ArtemisLayout? GetDeviceLayout(ArtemisDevice device)
@ -19,7 +25,7 @@ public class NoneLayoutProvider : ILayoutProvider
/// <inheritdoc />
public bool IsMatch(ArtemisDevice device)
{
return device.LayoutSelection.Type == LayoutType;
return device.LayoutSelection.Type == LAYOUT_TYPE;
}
/// <summary>
@ -28,7 +34,7 @@ public class NoneLayoutProvider : ILayoutProvider
/// <param name="device">The device to apply the provider to.</param>
public void ConfigureDevice(ArtemisDevice device)
{
device.LayoutSelection.Type = LayoutType;
device.LayoutSelection.Type = LAYOUT_TYPE;
device.LayoutSelection.Parameter = null;
}
}

View File

@ -1,11 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using Artemis.Storage.Entities.Profile.Nodes;
using DryIoc;
using Newtonsoft.Json;
using SkiaSharp;
@ -13,31 +11,8 @@ namespace Artemis.Core.Services;
internal class NodeService : INodeService
{
#region Constants
private static readonly Type TypeNode = typeof(INode);
#endregion
private readonly IContainer _container;
#region Constructors
public NodeService(IContainer container)
{
_container = container;
}
#endregion
#region Properties & Fields
public IEnumerable<NodeData> AvailableNodes => NodeTypeStore.GetAll();
#endregion
#region Methods
/// <inheritdoc />
public List<Type> GetRegisteredTypes()
{
@ -53,7 +28,7 @@ internal class NodeService : INodeService
// Objects represent an input that can take any type, these are hardcoded white
if (type == typeof(object))
return new TypeColorRegistration(type, new SKColor(255, 255, 255, 255), Constants.CorePlugin);
return new TypeColorRegistration(type, new SKColor(255, 255, 255, 255), Constants.CorePluginFeature);
// Come up with a random color based on the type name that should be the same each time
MD5 md5Hasher = MD5.Create();
@ -61,32 +36,7 @@ internal class NodeService : INodeService
int hash = BitConverter.ToInt32(hashed, 0);
SKColor baseColor = SKColor.FromHsl(hash % 255, 50 + hash % 50, 50);
return new TypeColorRegistration(type, baseColor, Constants.CorePlugin);
}
public NodeTypeRegistration RegisterNodeType(Plugin plugin, Type nodeType)
{
if (plugin == null) throw new ArgumentNullException(nameof(plugin));
if (nodeType == null) throw new ArgumentNullException(nameof(nodeType));
if (!TypeNode.IsAssignableFrom(nodeType)) throw new ArgumentException("Node has to be a base type of the Node-Type.", nameof(nodeType));
NodeAttribute? nodeAttribute = nodeType.GetCustomAttribute<NodeAttribute>();
string name = nodeAttribute?.Name ?? nodeType.Name;
string description = nodeAttribute?.Description ?? string.Empty;
string category = nodeAttribute?.Category ?? string.Empty;
string helpUrl = nodeAttribute?.HelpUrl ?? string.Empty;
NodeData nodeData = new(plugin, nodeType, name, description, category, helpUrl, nodeAttribute?.InputType, nodeAttribute?.OutputType, (s, e) => CreateNode(s, e, nodeType));
return NodeTypeStore.Add(nodeData);
}
public TypeColorRegistration RegisterTypeColor(Plugin plugin, Type type, SKColor color)
{
if (plugin == null) throw new ArgumentNullException(nameof(plugin));
if (type == null) throw new ArgumentNullException(nameof(type));
return NodeTypeStore.AddColor(type, color, plugin);
return new TypeColorRegistration(type, baseColor, Constants.CorePluginFeature);
}
public string ExportScript(NodeScript nodeScript)
@ -103,33 +53,6 @@ internal class NodeService : INodeService
target.LoadFromEntity(entity);
}
private INode CreateNode(INodeScript script, NodeEntity? entity, Type nodeType)
{
INode node = _container.Resolve(nodeType) as INode ?? throw new InvalidOperationException($"Node {nodeType} is not an INode");
if (node is Node concreteNode)
concreteNode.Container = _container;
if (entity != null)
{
node.X = entity.X;
node.Y = entity.Y;
try
{
if (node is Node nodeImplementation)
nodeImplementation.DeserializeStorage(entity.Storage);
}
catch
{
// ignored
}
}
node.TryInitialize(script);
return node;
}
#endregion
}
/// <summary>
@ -153,21 +76,6 @@ public interface INodeService : IArtemisService
/// </summary>
TypeColorRegistration GetTypeColorRegistration(Type type);
/// <summary>
/// Registers a node of the provided <paramref name="nodeType" />
/// </summary>
/// <param name="plugin">The plugin the node belongs to</param>
/// <param name="nodeType">The type of node to initialize</param>
NodeTypeRegistration RegisterNodeType(Plugin plugin, Type nodeType);
/// <summary>
/// Registers a type with a provided color for use in the node editor
/// </summary>
/// <param name="plugin">The plugin making the registration</param>
/// <param name="type">The type to associate the color with</param>
/// <param name="color">The color to display</param>
TypeColorRegistration RegisterTypeColor(Plugin plugin, Type type, SKColor color);
/// <summary>
/// Exports the provided node script to JSON.
/// </summary>

View File

@ -7,6 +7,9 @@ using System.Threading;
namespace Artemis.Core.Services;
/// <summary>
/// Represents a monitor that efficiently keeps track of running processes.
/// </summary>
public static partial class ProcessMonitor
{
#region Properties & Fields
@ -15,8 +18,11 @@ public static partial class ProcessMonitor
private static Timer? _timer;
private static Dictionary<int, ProcessInfo> _processes = new();
private static readonly Dictionary<int, ProcessInfo> _processes = new();
/// <summary>
/// Gets an immutable array of the current processes.
/// </summary>
public static ImmutableArray<ProcessInfo> Processes
{
get
@ -25,9 +31,17 @@ public static partial class ProcessMonitor
return _processes.Values.ToImmutableArray();
}
}
/// <summary>
/// Gets the date time at which the last update took place.
/// </summary>
public static DateTime LastUpdate { get; private set; }
private static TimeSpan _updateInterval = TimeSpan.FromSeconds(1);
/// <summary>
/// Gets or sets the interval at which to update the list of processes.
/// </summary>
public static TimeSpan UpdateInterval
{
get => _updateInterval;
@ -40,6 +54,9 @@ public static partial class ProcessMonitor
}
}
/// <summary>
/// Gets a value indicating whether the monitoring has started.
/// </summary>
public static bool IsStarted
{
get
@ -53,7 +70,14 @@ public static partial class ProcessMonitor
#region Events
/// <summary>
/// Occurs when a new process is started.
/// </summary>
public static event EventHandler<ProcessEventArgs>? ProcessStarted;
/// <summary>
/// Occurs when a process is stopped.
/// </summary>
public static event EventHandler<ProcessEventArgs>? ProcessStopped;
#endregion
@ -69,6 +93,9 @@ public static partial class ProcessMonitor
#region Methods
/// <summary>
/// Starts monitoring processes.
/// </summary>
public static void Start()
{
lock (LOCK)
@ -87,6 +114,9 @@ public static partial class ProcessMonitor
}
}
/// <summary>
/// Stops monitoring processes.
/// </summary>
public static void Stop()
{
lock (LOCK)
@ -100,7 +130,7 @@ public static partial class ProcessMonitor
FreeBuffer();
}
}
/// <summary>
/// Returns whether the specified process is running
/// </summary>
@ -111,7 +141,7 @@ public static partial class ProcessMonitor
{
if (!IsStarted || (processName == null && processLocation == null))
return false;
lock (LOCK)
{
return _processes.Values.Any(x => IsProcessRunning(x, processName, processLocation));
@ -130,19 +160,19 @@ public static partial class ProcessMonitor
OnProcessStopped(info);
}
}
private static bool IsProcessRunning(ProcessInfo info, string? processName, string? processLocation)
{
if (processName != null && processLocation != null)
return string.Equals(info.ProcessName, processName, StringComparison.InvariantCultureIgnoreCase) &&
string.Equals(Path.GetDirectoryName(info.Executable), processLocation, StringComparison.InvariantCultureIgnoreCase);
if (processName != null)
return string.Equals(info.ProcessName, processName, StringComparison.InvariantCultureIgnoreCase);
if (processLocation != null)
return string.Equals(Path.GetDirectoryName(info.Executable), processLocation, StringComparison.InvariantCultureIgnoreCase);
return false;
}
@ -152,7 +182,10 @@ public static partial class ProcessMonitor
{
ProcessStarted?.Invoke(null, new ProcessEventArgs(processInfo));
}
catch { /* Subscribers are idiots! */ }
catch
{
/* Subscribers are idiots! */
}
}
private static void OnProcessStopped(ProcessInfo processInfo)
@ -161,7 +194,10 @@ public static partial class ProcessMonitor
{
ProcessStopped?.Invoke(null, new ProcessEventArgs(processInfo));
}
catch { /* Subscribers are idiots! */ }
catch
{
/* Subscribers are idiots! */
}
}
#endregion

View File

@ -42,6 +42,7 @@ internal class LayerBrushService : ILayerBrushService
BrushType = "SolidBrush"
});
defaultReference.Value ??= new LayerBrushReference();
defaultReference.Value.LayerBrushProviderId ??= "Artemis.Plugins.LayerBrushes.Color.ColorBrushProvider-92a9d6ba";
defaultReference.Value.BrushType ??= "SolidBrush";
return LayerBrushStore.Get(defaultReference.Value.LayerBrushProviderId, defaultReference.Value.BrushType)?.LayerBrushDescriptor;

View File

@ -8,8 +8,10 @@ using System.Text;
using System.Threading.Tasks;
using Artemis.Core.Modules;
using Artemis.Storage.Entities.Profile;
using Artemis.Storage.Migrations;
using Artemis.Storage.Repositories.Interfaces;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Serilog;
using SkiaSharp;
@ -24,9 +26,10 @@ internal class ProfileService : IProfileService
private readonly List<ArtemisKeyboardKeyEventArgs> _pendingKeyboardEvents = new();
private readonly List<ProfileCategory> _profileCategories;
private readonly IProfileRepository _profileRepository;
private readonly List<IProfileMigration> _profileMigrators;
private readonly List<Exception> _renderExceptions = new();
private readonly List<Exception> _updateExceptions = new();
private DateTime _lastRenderExceptionLog;
private DateTime _lastUpdateExceptionLog;
@ -35,13 +38,15 @@ internal class ProfileService : IProfileService
IPluginManagementService pluginManagementService,
IInputService inputService,
IDeviceService deviceService,
IProfileRepository profileRepository)
IProfileRepository profileRepository,
List<IProfileMigration> profileMigrators)
{
_logger = logger;
_profileCategoryRepository = profileCategoryRepository;
_pluginManagementService = pluginManagementService;
_deviceService = deviceService;
_profileRepository = profileRepository;
_profileMigrators = profileMigrators;
_profileCategories = new List<ProfileCategory>(_profileCategoryRepository.GetAll().Select(c => new ProfileCategory(c)).OrderBy(c => c.Order));
_deviceService.LedsChanged += DeviceServiceOnLedsChanged;
@ -58,7 +63,7 @@ internal class ProfileService : IProfileService
public ProfileConfiguration? FocusProfile { get; set; }
public ProfileElement? FocusProfileElement { get; set; }
public bool UpdateFocusProfile { get; set; }
public bool ProfileRenderingDisabled { get; set; }
/// <inheritdoc />
@ -221,7 +226,7 @@ internal class ProfileService : IProfileService
return profileConfiguration.Profile;
}
ProfileEntity profileEntity;
ProfileEntity? profileEntity;
try
{
profileEntity = _profileRepository.Get(profileConfiguration.Entity.ProfileId);
@ -280,7 +285,7 @@ internal class ProfileService : IProfileService
{
DeactivateProfile(profileConfiguration);
ProfileEntity profileEntity = _profileRepository.Get(profileConfiguration.Entity.ProfileId);
ProfileEntity? profileEntity = _profileRepository.Get(profileConfiguration.Entity.ProfileId);
if (profileEntity == null)
return;
@ -353,7 +358,7 @@ internal class ProfileService : IProfileService
DeactivateProfile(profileConfiguration);
SaveProfileCategory(profileConfiguration.Category);
ProfileEntity profileEntity = _profileRepository.Get(profileConfiguration.Entity.ProfileId);
ProfileEntity? profileEntity = _profileRepository.Get(profileConfiguration.Entity.ProfileId);
if (profileEntity != null)
_profileRepository.Remove(profileEntity);
@ -461,7 +466,12 @@ internal class ProfileService : IProfileService
await using Stream profileStream = profileEntry.Open();
using StreamReader profileReader = new(profileStream);
ProfileEntity? profileEntity = JsonConvert.DeserializeObject<ProfileEntity>(await profileReader.ReadToEndAsync(), IProfileService.ExportSettings);
JObject? profileJson = JsonConvert.DeserializeObject<JObject>(await profileReader.ReadToEndAsync(), IProfileService.ExportSettings);
// Before deserializing, apply any pending migrations
MigrateProfile(configurationEntity, profileJson);
ProfileEntity? profileEntity = profileJson?.ToObject<ProfileEntity>(JsonSerializer.Create(IProfileService.ExportSettings));
if (profileEntity == null)
throw new ArtemisCoreException("Could not import profile, failed to deserialize profile.json");
@ -514,10 +524,10 @@ internal class ProfileService : IProfileService
public async Task<ProfileConfiguration> OverwriteProfile(MemoryStream archiveStream, ProfileConfiguration profileConfiguration)
{
ProfileConfiguration imported = await ImportProfile(archiveStream, profileConfiguration.Category, true, true, null, profileConfiguration.Order + 1);
DeleteProfile(profileConfiguration);
SaveProfileCategory(imported.Category);
return imported;
}
@ -545,6 +555,21 @@ internal class ProfileService : IProfileService
}
}
private void MigrateProfile(ProfileConfigurationEntity configurationEntity, JObject? profileJson)
{
if (profileJson == null)
return;
foreach (IProfileMigration profileMigrator in _profileMigrators.OrderBy(m => m.Version))
{
if (profileMigrator.Version <= configurationEntity.Version)
continue;
profileMigrator.Migrate(profileJson);
configurationEntity.Version = profileMigrator.Version;
}
}
/// <summary>
/// Populates all missing LEDs on all currently active profiles
/// </summary>

View File

@ -100,7 +100,7 @@ public interface IWebServerService : IArtemisService
/// <summary>
/// Removes an existing Web API controller and restarts the web server
/// </summary>
/// <typeparam name="T">The type of Web API controller to remove</typeparam>
/// <param name="registration">The registration of the controller to remove.</param>
void RemoveController(WebApiControllerRegistration registration);
/// <summary>

View File

@ -13,16 +13,13 @@ internal class NodeTypeStore
public static NodeTypeRegistration Add(NodeData nodeData)
{
if (nodeData.Plugin == null)
throw new ArtemisCoreException("Cannot add a data binding modifier type that is not associated with a plugin");
NodeTypeRegistration typeRegistration;
lock (Registrations)
{
if (Registrations.Any(r => r.NodeData == nodeData))
throw new ArtemisCoreException($"Data binding modifier type store already contains modifier '{nodeData.Name}'");
typeRegistration = new NodeTypeRegistration(nodeData, nodeData.Plugin) {IsInStore = true};
typeRegistration = new NodeTypeRegistration(nodeData, nodeData.Provider) {IsInStore = true};
Registrations.Add(typeRegistration);
}
@ -60,24 +57,12 @@ internal class NodeTypeStore
}
}
public static Plugin? GetPlugin(INode node)
{
Type nodeType = node.GetType();
lock (Registrations)
{
return Registrations.FirstOrDefault(r => r.NodeData.Type == nodeType)?.Plugin;
}
}
public static TypeColorRegistration AddColor(Type type, SKColor color, Plugin plugin)
public static TypeColorRegistration AddColor(Type type, SKColor color, PluginFeature pluginFeature)
{
TypeColorRegistration typeColorRegistration;
lock (ColorRegistrations)
{
if (ColorRegistrations.Any(r => r.Type == type))
throw new ArtemisCoreException($"Node color store already contains a color for '{type.Name}'");
typeColorRegistration = new TypeColorRegistration(type, color, plugin) {IsInStore = true};
typeColorRegistration = new TypeColorRegistration(type, color, pluginFeature) {IsInStore = true};
ColorRegistrations.Add(typeColorRegistration);
}

View File

@ -9,12 +9,12 @@ namespace Artemis.Core;
/// </summary>
public class NodeTypeRegistration
{
internal NodeTypeRegistration(NodeData nodeData, Plugin plugin)
internal NodeTypeRegistration(NodeData nodeData, PluginFeature pluginFeature)
{
NodeData = nodeData;
Plugin = plugin;
PluginFeature = pluginFeature;
Plugin.Disabled += OnDisabled;
PluginFeature.Disabled += OnDisabled;
}
/// <summary>
@ -23,9 +23,9 @@ public class NodeTypeRegistration
public NodeData NodeData { get; }
/// <summary>
/// Gets the plugin the node is associated with
/// Gets the plugin feature the node is associated with
/// </summary>
public Plugin Plugin { get; }
public PluginFeature PluginFeature { get; }
/// <summary>
/// Gets a boolean indicating whether the registration is in the internal Core store
@ -39,12 +39,12 @@ public class NodeTypeRegistration
/// <returns><see langword="true" /> if the entity matches this registration; otherwise <see langword="false" />.</returns>
public bool MatchesEntity(NodeEntity entity)
{
return Plugin.Guid == entity.PluginId && NodeData.Type.Name == entity.Type;
return PluginFeature.Id == entity.ProviderId && NodeData.Type.Name == entity.Type;
}
private void OnDisabled(object? sender, EventArgs e)
{
Plugin.Disabled -= OnDisabled;
PluginFeature.Disabled -= OnDisabled;
if (IsInStore)
NodeTypeStore.Remove(this);
}
@ -55,13 +55,13 @@ public class NodeTypeRegistration
/// </summary>
public class TypeColorRegistration
{
internal TypeColorRegistration(Type type, SKColor color, Plugin plugin)
internal TypeColorRegistration(Type type, SKColor color, PluginFeature pluginFeature)
{
Type = type;
Color = color;
Plugin = plugin;
PluginFeature = pluginFeature;
Plugin.Disabled += OnDisabled;
PluginFeature.Disabled += OnDisabled;
}
/// <summary>
@ -80,9 +80,9 @@ public class TypeColorRegistration
public SKColor DarkenedColor => Color.Darken(0.35f);
/// <summary>
/// Gets the plugin type color is associated with
/// Gets the plugin feature this type color is associated with
/// </summary>
public Plugin Plugin { get; }
public PluginFeature PluginFeature { get; }
/// <summary>
/// Gets a boolean indicating whether the registration is in the internal Core store
@ -91,7 +91,7 @@ public class TypeColorRegistration
private void OnDisabled(object? sender, EventArgs e)
{
Plugin.Disabled -= OnDisabled;
PluginFeature.Disabled -= OnDisabled;
if (IsInStore)
NodeTypeStore.RemoveColor(this);
}

View File

@ -14,6 +14,11 @@ public interface INode : INotifyPropertyChanged, IBreakableModel
/// Gets or sets the ID of the node.
/// </summary>
Guid Id { get; set; }
/// <summary>
/// Gets or sets the node data with information about this node
/// </summary>
NodeData? NodeData { get; set; }
/// <summary>
/// Gets the name of the node

View File

@ -1,4 +1,5 @@
using System;
using Artemis.Core.Nodes;
using Artemis.Storage.Entities.Profile.Nodes;
namespace Artemis.Core;
@ -10,9 +11,9 @@ public class NodeData
{
#region Constructors
internal NodeData(Plugin plugin, Type type, string name, string description, string category, string helpUrl, Type? inputType, Type? outputType, Func<INodeScript, NodeEntity?, INode> create)
internal NodeData(NodeProvider provider, Type type, string name, string description, string category, string helpUrl, Type? inputType, Type? outputType)
{
Plugin = plugin;
Provider = provider;
Type = type;
Name = name;
Description = description;
@ -20,7 +21,6 @@ public class NodeData
HelpUrl = helpUrl;
InputType = inputType;
OutputType = outputType;
_create = create;
}
#endregion
@ -35,14 +35,31 @@ public class NodeData
/// <returns>The returning node of type <see cref="Type" /></returns>
public INode CreateNode(INodeScript script, NodeEntity? entity)
{
INode node = _create(script, entity);
INode node = (INode) Provider.Plugin.Resolve(Type);
node.NodeData = this;
if (string.IsNullOrWhiteSpace(node.Name))
node.Name = Name;
if (string.IsNullOrWhiteSpace(node.Description))
node.Description = Description;
if (string.IsNullOrWhiteSpace(node.HelpUrl))
node.HelpUrl = HelpUrl;
if (entity != null)
{
node.X = entity.X;
node.Y = entity.Y;
try
{
if (node is Node nodeImplementation)
nodeImplementation.DeserializeStorage(entity.Storage);
}
catch
{
// ignored
}
}
node.TryInitialize(script);
return node;
}
@ -91,11 +108,11 @@ public class NodeData
}
#region Properties & Fields
/// <summary>
/// Gets the plugin that provided this node data
/// Gets the node provider that provided this node data
/// </summary>
public Plugin Plugin { get; }
public NodeProvider Provider { get; }
/// <summary>
/// Gets the type of <see cref="INode" /> this data represents
@ -132,7 +149,5 @@ public class NodeData
/// </summary>
public Type? OutputType { get; }
private readonly Func<INodeScript, NodeEntity?, INode> _create;
#endregion
}

View File

@ -161,6 +161,7 @@ public abstract class NodeScript : CorePropertyChanged, INodeScript
{
foreach (INode node in _nodes)
{
// ReSharper disable once SuspiciousTypeConversion.Global - Provided by plugins
if (node is IDisposable disposable)
disposable.Dispose();
}
@ -181,6 +182,7 @@ public abstract class NodeScript : CorePropertyChanged, INodeScript
foreach (INode removeNode in removeNodes)
{
RemoveNode(removeNode);
// ReSharper disable once SuspiciousTypeConversion.Global - Provided by plugins
if (removeNode is IDisposable disposable)
disposable.Dispose();
}
@ -312,7 +314,7 @@ public abstract class NodeScript : CorePropertyChanged, INodeScript
NodeEntity nodeEntity = new()
{
Id = node.Id,
PluginId = NodeTypeStore.GetPlugin(node)?.Guid ?? Constants.CorePlugin.Guid,
ProviderId = node.NodeData?.Provider.Id ?? Constants.CorePluginFeature.Id,
Type = node.GetType().Name,
X = node.X,
Y = node.Y,

View File

@ -41,6 +41,9 @@ public abstract class Node : BreakableModel, INode
set => SetAndNotify(ref _id, value);
}
/// <inheritdoc />
public NodeData? NodeData { get; set; }
private string _name;
/// <inheritdoc />
@ -104,8 +107,6 @@ public abstract class Node : BreakableModel, INode
/// <inheritdoc />
public override string BrokenDisplayName => Name;
internal IContainer Container { get; set; } = null!;
#endregion
#region Construtors

View File

@ -25,7 +25,9 @@ public abstract class Node<TStorage, TViewModel> : Node<TStorage>, ICustomViewMo
/// <param name="nodeScript"></param>
public virtual TViewModel GetViewModel(NodeScript nodeScript)
{
return Container.Resolve<TViewModel>(args: new object[] {this, nodeScript});
if (NodeData == null)
throw new ArtemisCoreException("Nodes without node data (default nodes or exit nodes) cannot have custom view models");
return NodeData.Provider.Plugin.Container.Resolve<TViewModel>(args: new object[] {this, nodeScript});
}
/// <summary>

View File

@ -1,12 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<PreserveCompilationContext>false</PreserveCompilationContext>
<Platforms>x64</Platforms>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="LiteDB" Version="5.0.17" />
<PackageReference Include="Serilog" Version="3.1.1" />
<PackageReference Include="LiteDB" />
<PackageReference Include="Serilog" />
<PackageReference Include="Newtonsoft.Json" />
</ItemGroup>
</Project>

View File

@ -11,7 +11,7 @@ public class QueuedActionEntity
}
public Guid Id { get; set; }
public string Type { get; set; }
public string Type { get; set; } = string.Empty;
public DateTimeOffset CreatedAt { get; set; }
public Dictionary<string, object> Parameters { get; set; }

View File

@ -6,6 +6,6 @@ public class ReleaseEntity
{
public Guid Id { get; set; }
public string Version { get; set; }
public string Version { get; set; } = string.Empty;
public DateTimeOffset? InstalledAt { get; set; }
}

View File

@ -6,7 +6,7 @@ public class ScriptConfigurationEntity
{
public Guid Id { get; set; }
public string Name { get; set; }
public string ScriptingProviderId { get; set; }
public string ScriptContent { get; set; }
public string Name { get; set; } = string.Empty;
public string ScriptingProviderId { get; set; } = string.Empty;
public string? ScriptContent { get; set; }
}

View File

@ -24,6 +24,6 @@ public class PluginEntity
/// </summary>
public class PluginFeatureEntity
{
public string Type { get; set; }
public string Type { get; set; } = string.Empty;
public bool IsEnabled { get; set; }
}

View File

@ -10,6 +10,6 @@ public class PluginSettingEntity
public Guid Id { get; set; }
public Guid PluginGuid { get; set; }
public string Name { get; set; }
public string Value { get; set; }
public string Name { get; set; } = string.Empty;
public string Value { get; set; } = string.Empty;
}

View File

@ -1,8 +0,0 @@
using System.Collections.Generic;
namespace Artemis.Storage.Entities.Profile.Abstract;
public abstract class DataModelConditionPartEntity
{
public List<DataModelConditionPartEntity> Children { get; set; }
}

View File

@ -8,8 +8,8 @@ public abstract class RenderElementEntity
public Guid Id { get; set; }
public Guid ParentId { get; set; }
public List<LayerEffectEntity> LayerEffects { get; set; }
public List<LayerEffectEntity> LayerEffects { get; set; } = new();
public IConditionEntity DisplayCondition { get; set; }
public TimelineEntity Timeline { get; set; }
public IConditionEntity? DisplayCondition { get; set; }
public TimelineEntity? Timeline { get; set; }
}

View File

@ -1,5 +1,3 @@
namespace Artemis.Storage.Entities.Profile.AdaptionHints;
public interface IAdaptionHintEntity
{
}
public interface IAdaptionHintEntity;

View File

@ -2,6 +2,4 @@
namespace Artemis.Storage.Entities.Profile.Conditions;
public class AlwaysOnConditionEntity : IConditionEntity
{
}
public class AlwaysOnConditionEntity : IConditionEntity;

View File

@ -8,6 +8,6 @@ public class EventConditionEntity : IConditionEntity
public int TriggerMode { get; set; }
public int OverlapMode { get; set; }
public int ToggleOffMode { get; set; }
public DataModelPathEntity EventPath { get; set; }
public NodeScriptEntity Script { get; set; }
public DataModelPathEntity? EventPath { get; set; }
public NodeScriptEntity? Script { get; set; }
}

View File

@ -1,5 +1,3 @@
namespace Artemis.Storage.Entities.Profile.Abstract;
public interface IConditionEntity
{
}
public interface IConditionEntity;

View File

@ -2,6 +2,4 @@
namespace Artemis.Storage.Entities.Profile.Conditions;
public class PlayOnceConditionEntity : IConditionEntity
{
}
public class PlayOnceConditionEntity : IConditionEntity;

View File

@ -7,5 +7,5 @@ public class StaticConditionEntity : IConditionEntity
{
public int PlayMode { get; set; }
public int StopMode { get; set; }
public NodeScriptEntity Script { get; set; }
public NodeScriptEntity? Script { get; set; }
}

View File

@ -4,7 +4,6 @@ namespace Artemis.Storage.Entities.Profile.DataBindings;
public class DataBindingEntity
{
public string Identifier { get; set; }
public bool IsEnabled { get; set; }
public NodeScriptEntity NodeScript { get; set; }
public NodeScriptEntity? NodeScript { get; set; }
}

View File

@ -2,7 +2,7 @@
public class DataModelPathEntity
{
public string Path { get; set; }
public string DataModelId { get; set; }
public string Type { get; set; }
public string Path { get; set; } = string.Empty;
public string? DataModelId { get; set; }
public string? Type { get; set; }
}

View File

@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using Artemis.Storage.Entities.Profile.Abstract;
using LiteDB;
@ -7,18 +6,13 @@ namespace Artemis.Storage.Entities.Profile;
public class FolderEntity : RenderElementEntity
{
public FolderEntity()
{
LayerEffects = new List<LayerEffectEntity>();
}
public int Order { get; set; }
public string Name { get; set; }
public string? Name { get; set; }
public bool IsExpanded { get; set; }
public bool Suspended { get; set; }
[BsonRef("ProfileEntity")]
public ProfileEntity Profile { get; set; }
public ProfileEntity Profile { get; set; } = null!;
public Guid ProfileId { get; set; }
}

View File

@ -6,6 +6,6 @@ public class KeyframeEntity
{
public TimeSpan Position { get; set; }
public int Timeline { get; set; }
public string Value { get; set; }
public string Value { get; set; } = string.Empty;
public int EasingFunction { get; set; }
}

View File

@ -2,8 +2,8 @@
public class LayerBrushEntity
{
public string ProviderId { get; set; }
public string BrushType { get; set; }
public string ProviderId { get; set; } = string.Empty;
public string BrushType { get; set; } = string.Empty;
public PropertyGroupEntity PropertyGroup { get; set; }
public PropertyGroupEntity? PropertyGroup { get; set; }
}

View File

@ -2,11 +2,11 @@
public class LayerEffectEntity
{
public string ProviderId { get; set; }
public string EffectType { get; set; }
public string Name { get; set; }
public string ProviderId { get; set; } = string.Empty;
public string EffectType { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public bool HasBeenRenamed { get; set; }
public int Order { get; set; }
public PropertyGroupEntity PropertyGroup { get; set; }
public PropertyGroupEntity? PropertyGroup { get; set; }
}

View File

@ -12,22 +12,21 @@ public class LayerEntity : RenderElementEntity
{
Leds = new List<LedEntity>();
AdaptionHints = new List<IAdaptionHintEntity>();
LayerEffects = new List<LayerEffectEntity>();
}
public int Order { get; set; }
public string Name { get; set; }
public string? Name { get; set; }
public bool Suspended { get; set; }
public List<LedEntity> Leds { get; set; }
public List<IAdaptionHintEntity> AdaptionHints { get; set; }
public PropertyGroupEntity GeneralPropertyGroup { get; set; }
public PropertyGroupEntity TransformPropertyGroup { get; set; }
public LayerBrushEntity LayerBrush { get; set; }
public PropertyGroupEntity? GeneralPropertyGroup { get; set; }
public PropertyGroupEntity? TransformPropertyGroup { get; set; }
public LayerBrushEntity? LayerBrush { get; set; }
[BsonRef("ProfileEntity")]
public ProfileEntity Profile { get; set; }
public ProfileEntity Profile { get; set; } = null!;
public Guid ProfileId { get; set; }
}

View File

@ -5,8 +5,8 @@ namespace Artemis.Storage.Entities.Profile;
public class LedEntity
{
public string LedName { get; set; }
public string DeviceIdentifier { get; set; }
public string LedName { get; set; } = string.Empty;
public string DeviceIdentifier { get; set; } = string.Empty;
public int? PhysicalLayout { get; set; }
@ -14,7 +14,7 @@ public class LedEntity
private sealed class LedEntityEqualityComparer : IEqualityComparer<LedEntity>
{
public bool Equals(LedEntity x, LedEntity y)
public bool Equals(LedEntity? x, LedEntity? y)
{
if (ReferenceEquals(x, y))
return true;

View File

@ -20,12 +20,12 @@ public class NodeConnectionEntity
TargetPinId = nodeConnectionEntity.TargetPinId;
}
public string SourceType { get; set; }
public string SourceType { get; set; } = string.Empty;
public Guid SourceNode { get; set; }
public Guid TargetNode { get; set; }
public int SourcePinCollectionId { get; set; }
public int SourcePinId { get; set; }
public string TargetType { get; set; }
public string TargetType { get; set; } = string.Empty;
public int TargetPinCollectionId { get; set; }
public int TargetPinId { get; set; }
}

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
@ -15,7 +15,7 @@ public class NodeEntity
{
Id = nodeEntity.Id;
Type = nodeEntity.Type;
PluginId = nodeEntity.PluginId;
ProviderId = nodeEntity.ProviderId;
Name = nodeEntity.Name;
Description = nodeEntity.Description;
@ -28,15 +28,15 @@ public class NodeEntity
}
public Guid Id { get; set; }
public string Type { get; set; }
public Guid PluginId { get; set; }
public string Type { get; set; } = string.Empty;
public string ProviderId { get; set; } = string.Empty;
public string Name { get; set; }
public string Description { get; set; }
public string Name { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public bool IsExitNode { get; set; }
public double X { get; set; }
public double Y { get; set; }
public string Storage { get; set; }
public string Storage { get; set; } = string.Empty;
public List<NodePinCollectionEntity> PinCollections { get; set; }
}

View File

@ -10,8 +10,8 @@ public class NodeScriptEntity
Connections = new List<NodeConnectionEntity>();
}
public string Name { get; set; }
public string Description { get; set; }
public string Name { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public List<NodeEntity> Nodes { get; set; }
public List<NodeConnectionEntity> Connections { get; set; }

View File

@ -7,7 +7,7 @@ public class ProfileCategoryEntity
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Name { get; set; } = string.Empty;
public bool IsCollapsed { get; set; }
public bool IsSuspended { get; set; }
public int Order { get; set; }

View File

@ -5,8 +5,8 @@ namespace Artemis.Storage.Entities.Profile;
public class ProfileConfigurationEntity
{
public string Name { get; set; }
public string MaterialIcon { get; set; }
public string Name { get; set; } = string.Empty;
public string? MaterialIcon { get; set; }
public Guid FileIconId { get; set; }
public int IconType { get; set; }
public bool IconFill { get; set; }
@ -14,16 +14,17 @@ public class ProfileConfigurationEntity
public bool IsSuspended { get; set; }
public int ActivationBehaviour { get; set; }
public NodeScriptEntity ActivationCondition { get; set; }
public NodeScriptEntity? ActivationCondition { get; set; }
public int HotkeyMode { get; set; }
public ProfileConfigurationHotkeyEntity EnableHotkey { get; set; }
public ProfileConfigurationHotkeyEntity DisableHotkey { get; set; }
public ProfileConfigurationHotkeyEntity? EnableHotkey { get; set; }
public ProfileConfigurationHotkeyEntity? DisableHotkey { get; set; }
public string ModuleId { get; set; }
public string? ModuleId { get; set; }
public Guid ProfileCategoryId { get; set; }
public Guid ProfileId { get; set; }
public bool FadeInAndOut { get; set; }
public int Version { get; set; }
}

View File

@ -16,7 +16,7 @@ public class ProfileEntity
public Guid Id { get; set; }
public string Name { get; set; }
public string Name { get; set; } = string.Empty;
public bool IsFreshImport { get; set; }
public List<FolderEntity> Folders { get; set; }
@ -28,7 +28,7 @@ public class ProfileEntity
Guid oldGuid = Id;
Id = guid;
FolderEntity rootFolder = Folders.FirstOrDefault(f => f.ParentId == oldGuid);
FolderEntity? rootFolder = Folders.FirstOrDefault(f => f.ParentId == oldGuid);
if (rootFolder != null)
rootFolder.ParentId = Id;
}

View File

@ -5,10 +5,10 @@ namespace Artemis.Storage.Entities.Profile;
public class PropertyEntity
{
public string Identifier { get; set; }
public string Value { get; set; }
public string Identifier { get; set; } = string.Empty;
public string Value { get; set; } = string.Empty;
public bool KeyframesEnabled { get; set; }
public DataBindingEntity DataBinding { get; set; }
public DataBindingEntity? DataBinding { get; set; }
public List<KeyframeEntity> KeyframeEntities { get; set; } = new();
}

View File

@ -4,7 +4,7 @@ namespace Artemis.Storage.Entities.Profile;
public class PropertyGroupEntity
{
public string Identifier { get; set; }
public string Identifier { get; set; } = string.Empty;
public List<PropertyEntity> Properties { get; set; } = new();
public List<PropertyGroupEntity> PropertyGroups { get; set; } = new();
}

View File

@ -11,8 +11,8 @@ public class DeviceEntity
Categories = new List<int>();
}
public string Id { get; set; }
public string DeviceProvider { get; set; }
public string Id { get; set; } = string.Empty;
public string DeviceProvider { get; set; } = string.Empty;
public float X { get; set; }
public float Y { get; set; }
public float Rotation { get; set; }
@ -24,9 +24,9 @@ public class DeviceEntity
public bool IsEnabled { get; set; }
public int PhysicalLayout { get; set; }
public string LogicalLayout { get; set; }
public string LayoutType { get; set; }
public string LayoutParameter { get; set; }
public string? LogicalLayout { get; set; }
public string? LayoutType { get; set; }
public string? LayoutParameter { get; set; }
public List<DeviceInputIdentifierEntity> InputIdentifiers { get; set; }
public List<InputMappingEntity> InputMappings { get; set; }
@ -41,6 +41,6 @@ public class InputMappingEntity
public class DeviceInputIdentifierEntity
{
public string InputProvider { get; set; }
public object Identifier { get; set; }
public string InputProvider { get; set; } = string.Empty;
public object Identifier { get; set; } = string.Empty;
}

View File

@ -10,13 +10,12 @@ public class EntryEntity
public long EntryId { get; set; }
public int EntryType { get; set; }
public string Author { get; set; }
public string Author { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public string Summary { get; set; } = string.Empty;
public long ReleaseId { get; set; }
public string ReleaseVersion { get; set; }
public string ReleaseVersion { get; set; } = string.Empty;
public DateTimeOffset InstalledAt { get; set; }
public Dictionary<string,object> Metadata { get; set; }
public Dictionary<string,object>? Metadata { get; set; }
}

View File

@ -0,0 +1,9 @@
using Newtonsoft.Json.Linq;
namespace Artemis.Storage.Migrations;
public interface IProfileMigration
{
int Version { get; }
void Migrate(JObject profileJson);
}

View File

@ -1,6 +1,6 @@
using LiteDB;
namespace Artemis.Storage.Migrations.Interfaces;
namespace Artemis.Storage.Migrations;
public interface IStorageMigration
{

View File

@ -0,0 +1,88 @@
using Newtonsoft.Json.Linq;
namespace Artemis.Storage.Migrations.Profile;
/// <summary>
/// Migrates nodes to be provider-based.
/// This requires giving them a ProviderId and updating the their namespaces to match the namespace of the new plugin.
/// </summary>
internal class M0001NodeProviders : IProfileMigration
{
/// <inheritdoc />
public int Version => 1;
/// <inheritdoc />
public void Migrate(JObject profileJson)
{
JArray? folders = (JArray?) profileJson["Folders"]?["$values"];
JArray? layers = (JArray?) profileJson["Layers"]?["$values"];
if (folders != null)
{
foreach (JToken folder in folders)
MigrateProfileElement(folder);
}
if (layers != null)
{
foreach (JToken layer in layers)
{
MigrateProfileElement(layer);
MigratePropertyGroup(layer["GeneralPropertyGroup"]);
MigratePropertyGroup(layer["TransformPropertyGroup"]);
MigratePropertyGroup(layer["LayerBrush"]?["PropertyGroup"]);
}
}
}
private void MigrateProfileElement(JToken profileElement)
{
JArray? layerEffects = (JArray?) profileElement["LayerEffects"]?["$values"];
if (layerEffects != null)
{
foreach (JToken layerEffect in layerEffects)
MigratePropertyGroup(layerEffect["PropertyGroup"]);
}
JToken? displayCondition = profileElement["DisplayCondition"];
if (displayCondition != null)
MigrateNodeScript(displayCondition["Script"]);
}
private void MigratePropertyGroup(JToken? propertyGroup)
{
if (propertyGroup == null || !propertyGroup.HasValues)
return;
JArray? properties = (JArray?) propertyGroup["Properties"]?["$values"];
JArray? propertyGroups = (JArray?) propertyGroup["PropertyGroups"]?["$values"];
if (properties != null)
{
foreach (JToken property in properties)
MigrateNodeScript(property["DataBinding"]?["NodeScript"]);
}
if (propertyGroups != null)
{
foreach (JToken childPropertyGroup in propertyGroups)
MigratePropertyGroup(childPropertyGroup);
}
}
private void MigrateNodeScript(JToken? nodeScript)
{
if (nodeScript == null || !nodeScript.HasValues)
return;
JArray? nodes = (JArray?) nodeScript["Nodes"]?["$values"];
if (nodes == null)
return;
foreach (JToken node in nodes)
{
node["Type"] = node["Type"]?.Value<string>()?.Replace("Artemis.VisualScripting.Nodes", "Artemis.Plugins.Nodes.General.Nodes");
node["ProviderId"] = "Artemis.Plugins.Nodes.General.GeneralNodesProvider-d9e1ee78";
}
}
}

View File

@ -1,9 +1,8 @@
using System.Collections.Generic;
using System.Linq;
using Artemis.Storage.Migrations.Interfaces;
using LiteDB;
namespace Artemis.Storage.Migrations;
namespace Artemis.Storage.Migrations.Storage;
public class M0020AvaloniaReset : IStorageMigration
{

View File

@ -3,18 +3,17 @@ using System.Collections.Generic;
using System.Linq;
using Artemis.Storage.Entities.Profile;
using Artemis.Storage.Entities.Profile.Nodes;
using Artemis.Storage.Migrations.Interfaces;
using LiteDB;
namespace Artemis.Storage.Migrations;
namespace Artemis.Storage.Migrations.Storage;
public class M0021GradientNodes : IStorageMigration
{
private void MigrateDataBinding(PropertyEntity property)
{
NodeScriptEntity script = property.DataBinding.NodeScript;
NodeEntity exitNode = script.Nodes.FirstOrDefault(s => s.IsExitNode);
if (exitNode == null)
NodeScriptEntity? script = property.DataBinding?.NodeScript;
NodeEntity? exitNode = script?.Nodes.FirstOrDefault(s => s.IsExitNode);
if (script == null || exitNode == null)
return;
// Create a new node at the same position of the exit node
@ -22,7 +21,7 @@ public class M0021GradientNodes : IStorageMigration
{
Id = Guid.NewGuid(),
Type = "ColorGradientNode",
PluginId = Guid.Parse("ffffffff-ffff-ffff-ffff-ffffffffffff"),
ProviderId = "Artemis.Plugins.Nodes.General.GeneralNodesProvider-d9e1ee78",
Name = "Color Gradient",
Description = "Outputs a color gradient with the given colors",
X = exitNode.X,
@ -59,8 +58,11 @@ public class M0021GradientNodes : IStorageMigration
exitNode.Y += 30;
}
private void MigrateDataBinding(PropertyGroupEntity propertyGroup)
private void MigrateDataBinding(PropertyGroupEntity? propertyGroup)
{
if (propertyGroup == null)
return;
foreach (PropertyGroupEntity propertyGroupPropertyGroup in propertyGroup.PropertyGroups)
MigrateDataBinding(propertyGroupPropertyGroup);
@ -80,7 +82,7 @@ public class M0021GradientNodes : IStorageMigration
foreach (ProfileEntity profileEntity in profiles)
{
foreach (LayerEntity layer in profileEntity.Layers.Where(le => le.LayerBrush != null))
MigrateDataBinding(layer.LayerBrush.PropertyGroup);
MigrateDataBinding(layer.LayerBrush?.PropertyGroup);
repository.Update(profileEntity);
}

View File

@ -3,14 +3,13 @@ using Artemis.Storage.Entities.Profile;
using Artemis.Storage.Entities.Profile.Abstract;
using Artemis.Storage.Entities.Profile.Conditions;
using Artemis.Storage.Entities.Profile.Nodes;
using Artemis.Storage.Migrations.Interfaces;
using LiteDB;
namespace Artemis.Storage.Migrations;
namespace Artemis.Storage.Migrations.Storage;
public class M0022TransitionNodes : IStorageMigration
{
private void MigrateNodeScript(NodeScriptEntity nodeScript)
private void MigrateNodeScript(NodeScriptEntity? nodeScript)
{
if (nodeScript == null)
return;
@ -28,7 +27,7 @@ public class M0022TransitionNodes : IStorageMigration
}
}
private void MigratePropertyGroup(PropertyGroupEntity propertyGroup)
private void MigratePropertyGroup(PropertyGroupEntity? propertyGroup)
{
if (propertyGroup == null)
return;
@ -39,7 +38,7 @@ public class M0022TransitionNodes : IStorageMigration
MigrateNodeScript(property.DataBinding?.NodeScript);
}
private void MigrateDisplayCondition(IConditionEntity conditionEntity)
private void MigrateDisplayCondition(IConditionEntity? conditionEntity)
{
if (conditionEntity is EventConditionEntity eventConditionEntity)
MigrateNodeScript(eventConditionEntity.Script);
@ -70,14 +69,14 @@ public class M0022TransitionNodes : IStorageMigration
MigratePropertyGroup(layer.GeneralPropertyGroup);
MigratePropertyGroup(layer.TransformPropertyGroup);
foreach (LayerEffectEntity layerEffectEntity in layer.LayerEffects)
MigratePropertyGroup(layerEffectEntity?.PropertyGroup);
MigratePropertyGroup(layerEffectEntity.PropertyGroup);
MigrateDisplayCondition(layer.DisplayCondition);
}
foreach (FolderEntity folder in profileEntity.Folders)
{
foreach (LayerEffectEntity folderLayerEffect in folder.LayerEffects)
MigratePropertyGroup(folderLayerEffect?.PropertyGroup);
MigratePropertyGroup(folderLayerEffect.PropertyGroup);
MigrateDisplayCondition(folder.DisplayCondition);
}

View File

@ -1,8 +1,7 @@
using System.Collections.Generic;
using Artemis.Storage.Migrations.Interfaces;
using LiteDB;
namespace Artemis.Storage.Migrations;
namespace Artemis.Storage.Migrations.Storage;
public class M0023LayoutProviders : IStorageMigration
{

View File

@ -0,0 +1,100 @@
using System.Collections.Generic;
using Artemis.Storage.Entities.Profile;
using LiteDB;
namespace Artemis.Storage.Migrations.Storage;
public class M0024NodeProviders : IStorageMigration
{
public int UserVersion => 24;
public void Apply(LiteRepository repository)
{
List<ProfileCategoryEntity> profileCategories = repository.Query<ProfileCategoryEntity>().ToList();
foreach (ProfileCategoryEntity profileCategory in profileCategories)
{
foreach (ProfileConfigurationEntity profileConfigurationEntity in profileCategory.ProfileConfigurations)
{
profileConfigurationEntity.Version = 1;
}
repository.Update(profileCategory);
}
ILiteCollection<BsonDocument> collection = repository.Database.GetCollection("ProfileEntity");
foreach (BsonDocument profileBson in collection.FindAll())
{
BsonArray? folders = profileBson["Folders"]?.AsArray;
BsonArray? layers = profileBson["Layers"]?.AsArray;
if (folders != null)
{
foreach (BsonValue folder in folders)
MigrateProfileElement(folder.AsDocument);
}
if (layers != null)
{
foreach (BsonValue layer in layers)
{
MigrateProfileElement(layer.AsDocument);
MigratePropertyGroup(layer.AsDocument["GeneralPropertyGroup"].AsDocument);
MigratePropertyGroup(layer.AsDocument["TransformPropertyGroup"].AsDocument);
MigratePropertyGroup(layer.AsDocument["LayerBrush"]?["PropertyGroup"].AsDocument);
}
}
collection.Update(profileBson);
}
}
private void MigrateProfileElement(BsonDocument profileElement)
{
BsonArray? layerEffects = profileElement["LayerEffects"]?.AsArray;
if (layerEffects != null)
{
foreach (BsonValue layerEffect in layerEffects)
MigratePropertyGroup(layerEffect.AsDocument["PropertyGroup"].AsDocument);
}
BsonValue? displayCondition = profileElement["DisplayCondition"];
if (displayCondition != null)
MigrateNodeScript(displayCondition.AsDocument["Script"].AsDocument);
}
private void MigratePropertyGroup(BsonDocument? propertyGroup)
{
if (propertyGroup == null || propertyGroup.Keys.Count == 0)
return;
BsonArray? properties = propertyGroup["Properties"]?.AsArray;
BsonArray? propertyGroups = propertyGroup["PropertyGroups"]?.AsArray;
if (properties != null)
{
foreach (BsonValue property in properties)
MigrateNodeScript(property.AsDocument["DataBinding"]?["NodeScript"]?.AsDocument);
}
if (propertyGroups != null)
{
foreach (BsonValue childPropertyGroup in propertyGroups)
MigratePropertyGroup(childPropertyGroup.AsDocument);
}
}
private void MigrateNodeScript(BsonDocument? nodeScript)
{
if (nodeScript == null || nodeScript.Keys.Count == 0)
return;
BsonArray? nodes = nodeScript["Nodes"]?.AsArray;
if (nodes == null)
return;
foreach (BsonValue node in nodes)
{
node.AsDocument["Type"] = node.AsDocument["Type"]?.AsString?.Replace("Artemis.VisualScripting.Nodes", "Artemis.Plugins.Nodes.General.Nodes");
node.AsDocument["ProviderId"] = "Artemis.Plugins.Nodes.General.GeneralNodesProvider-d9e1ee78";
}
}
}

View File

@ -25,7 +25,7 @@ internal class DeviceRepository : IDeviceRepository
_repository.Delete<DeviceEntity>(deviceEntity.Id);
}
public DeviceEntity Get(string id)
public DeviceEntity? Get(string id)
{
return _repository.FirstOrDefault<DeviceEntity>(s => s.Id == id);
}

View File

@ -27,12 +27,12 @@ internal class EntryRepository : IEntryRepository
_repository.Delete<EntryEntity>(entryEntity.Id);
}
public EntryEntity Get(Guid id)
public EntryEntity? Get(Guid id)
{
return _repository.FirstOrDefault<EntryEntity>(s => s.Id == id);
}
public EntryEntity GetByEntryId(long entryId)
public EntryEntity? GetByEntryId(long entryId)
{
return _repository.FirstOrDefault<EntryEntity>(s => s.EntryId == entryId);
}

Some files were not shown because too many files have changed in this diff Show More