Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .vscode_template/extensions.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"ms-vscode.cmake-tools", // CMake support: configure, build, debug
"ms-vscode.cpptools", // C/C++ IntelliSense, jump to definition, etc.
"vadimcn.vscode-lldb", // LLDB debugger support
"emeraldwalk.RunOnSave" // Run scripts on file save (run formater uncristify)
],
"emeraldwalk.RunOnSave", // Run scripts on file save (run formater uncristify)
"llvm-vs-code-extensions.vscode-clangd" // Clangd language server for C/C++
]
}
12 changes: 10 additions & 2 deletions docs/apidocs_static/tutorials/1_getting_started.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ Fill out the manifest as follows:
"title": "My quick start",
"description": "This is a development extension for API research.",
"path": "main.js"
"actions": [
{
"path": "main.js"
}
]
}
```

Expand Down Expand Up @@ -57,7 +61,11 @@ Fill out the manifest as follows:
"title": "My quick start visual",
"description": "This is a development extension for API research.",
"path": "Main.qml"
"actions": [
{
"path": "Main.qml"
}
]
}
```

Expand Down
77 changes: 66 additions & 11 deletions docs/apidocs_static/tutorials/2_manifest.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ manifest.json
"ui_context": "Any",
"apiversion": 2,

"path": "main.js"
"actions": [
{
"path": "main.js"
}
]
}
```

Expand All @@ -37,17 +41,68 @@ Description of extension. Please write a one-sentence description to make it cle
### thumbnail (optional)
Thumbnail of extension. PNG format is supported.

### category (optional)
Category of extension.

* category (optional)- [Category of extension](#Categories)
* version (optional) - Version of extension.
* vendor (optional) - About the author
* ui_context (optional) - The context in which the extension is available
* ProjectOpened (default) - Project (score) open
* Any - always available
* apiversion (optional)- required api version
* 1 - api of old plugins, deprecated, don't use this for new extensions
* 2 (default) - actual api
* path (required) - path to main entry point, .js or .qml file
### version (optional)
Version of extension.

### vendor (optional)
About the author

### apiversion (optional)
The API version used:
* `1` - api of old plugins, deprecated, don't use this for new extensions
* `2` (default) - actual api

### ui_context (optional)
The context in which the extension is available:
* `ProjectOpened` (default) - Project (score) open
* `Any` - always available

### actions (required)
List of extension actions.
An extension typically has one action, but can have multiple.
Actions are displayed as menu items in `Menu->Plugins` by default.
However, they can also be displayed as buttons on the toolbar.

example
```
...
"actions": [
{"code": "configure", "type": "form", "title": "Configure", "path": "configure.qml"},
{"code": "add", "type": "macros", "title": "Add", "path": "add.js"},
{"code": "remove", "type": "macros", "title": "Remove", "path": "remove.js"}
]
```

#### action: code (optional)
Action code

#### action: type (required for type `composite`/optional for `form`/`macros`)
If an extension consists of forms and macros (type `composite`), then you must specify the type of each action. If the extension has a single action, or all actions are of the same type, then you don't need to specify it; the action type will be equivalent to the extension type.

#### action: title (optional)
The action title, if not specified, will be equivalent to the extension title.

#### action: icon (optional)
Action icon, displayed in the menu or on the toolbar.
see {@link Qml.IconCode|IconCode}

#### action: ui_context (optional)
The context in which the action is available.
If not specified, then equivalent to the extension `ui_context`.

#### action: show_on_appmenu (optional)
Whether to show the action in the menu. Default is `true`.

#### action: show_on_toolbar (optional)
Whether to show the action on the toolbar. Default is `false`.

#### action: path (required)
Path to the js script of macros or Qml form

#### action: func (optional)
For macros only. The name of the function to call.
This means you can specify the same script file but different functions for different actions.
If not specified, the default function name is `main`.
6 changes: 5 additions & 1 deletion docs/apidocs_static/tutorials/3_form.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ manifest.json
"type": "form",
...
"main": "Main.qml"
"actions": [
{
"path": "Main.qml"
}
]
}
```

Expand Down
6 changes: 5 additions & 1 deletion docs/apidocs_static/tutorials/4_macros.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ manifest.json
"type": "macros",
...
"main": "main.js"
"actions": [
{
"path": "main.js"
}
]
}
```

Expand Down
4 changes: 3 additions & 1 deletion share/extensions/colornotes/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,7 @@
"version": "3.5",
"apiversion": 1,

"path": "main.js"
"actions": [
{ "path": "main.js" }
]
}
5 changes: 4 additions & 1 deletion share/extensions/example1/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,8 @@
"ui_context": "Any",
"modal": true,

"path": "Main.qml"
"actions": [
{"path": "Main.qml"}
]

}
1 change: 0 additions & 1 deletion share/extensions/quickstart/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

"actions": [
{
"code": "main",
"path": "main.js"
}
]
Expand Down
6 changes: 5 additions & 1 deletion share/extensions/quickstart_v/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,9 @@
"title": "Quick start visual",
"description": "This is a development extension for API research.",

"path": "Main.qml"
"actions": [
{
"path": "Main.qml"
}
]
}
14 changes: 9 additions & 5 deletions src/framework/extensions/internal/extensioninstaller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,22 @@ void ExtensionInstaller::installExtension(const io::path_t& srcPath)
}

const ExtensionsLoader loader;
const Manifest m = loader.parseManifest(data);
const RetVal<Manifest> m = loader.parseManifest(data);
if (!m.ret) {
LOGE() << "failed parse manifest: " << srcPath << ", err: " << m.ret.toString();
return;
}

const Manifest existingManifest = provider()->manifest(m.uri);
const Manifest existingManifest = provider()->manifest(m.val.uri);
const bool alreadyInstalled = existingManifest.isValid();

if (!alreadyInstalled) {
doInstallExtension(srcPath);
return;
}

if (existingManifest.version == m.version) {
LOGI() << "already installed: " << m.uri;
if (existingManifest.version == m.val.version) {
LOGI() << "already installed: " << m.val.uri;

interactive()->info(trc("extensions", "The extension is already installed."), std::string(),
{ interactive()->buttonData(IInteractive::Button::Ok) });
Expand All @@ -68,7 +72,7 @@ void ExtensionInstaller::installExtension(const io::path_t& srcPath)

const std::string text = qtrc("extensions", "Another version of the extension “%1” is already installed (version %2). "
"Do you want to replace it with version %3?")
.arg(existingManifest.title, existingManifest.version, m.version).toStdString();
.arg(existingManifest.title, existingManifest.version, m.val.version).toStdString();

interactive()->question(trc("extensions", "Update extension"),
text,
Expand Down
109 changes: 73 additions & 36 deletions src/framework/extensions/internal/extensionsloader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
*/
#include "extensionsloader.h"

#include "extensionstypes.h"
#include "global/io/dir.h"
#include "global/io/file.h"
#include "global/io/fileinfo.h"
Expand All @@ -29,6 +30,7 @@
#include "global/runtime.h"

#include "log.h"
#include "types/ret.h"

using namespace muse;
using namespace muse::extensions;
Expand Down Expand Up @@ -80,7 +82,12 @@ ManifestList ExtensionsLoader::manifestList(const io::path_t& rootPath) const
io::paths_t paths = manifestPaths(rootPath);
for (const io::path_t& path : paths) {
LOGD() << "parsing manifest: " << path;
Manifest manifest = parseManifest(path);
RetVal<Manifest> rv = parseManifest(path);
if (!rv.ret) {
LOGE() << "failed parse manifest: " << path << ", err: " << rv.ret.toString();
continue;
}
Manifest& manifest = rv.val;
manifest.path = path;
resolvePaths(manifest, io::FileInfo(path).dirPath());
manifests.push_back(manifest);
Expand All @@ -98,71 +105,101 @@ io::paths_t ExtensionsLoader::manifestPaths(const io::path_t& rootPath) const
return paths.val;
}

Manifest ExtensionsLoader::parseManifest(const io::path_t& path) const
RetVal<Manifest> ExtensionsLoader::parseManifest(const io::path_t& path) const
{
ByteArray data;
Ret ret = io::File::readFile(path, data);
if (!ret) {
LOGE() << "failed read file: " << path << ", err: " << ret.toString();
return Manifest();
return RetVal<Manifest>::make_ret(ret);
}

return parseManifest(data);
}

Manifest ExtensionsLoader::parseManifest(const ByteArray& data) const
RetVal<Manifest> ExtensionsLoader::parseManifest(const ByteArray& data) const
{
std::string jsonErr;
JsonObject obj = JsonDocument::fromJson(data, &jsonErr).rootObject();
if (!jsonErr.empty()) {
LOGE() << "failed parse json, err: " << jsonErr;
return Manifest();
return RetVal<Manifest>::make_ret(Ret::Code::BadData,
"failed parse json, err: " + jsonErr
);
}

auto check_required = [](const JsonObject& obj, const std::string& key) {
if (!obj.contains(key)) {
return muse::make_ret(Ret::Code::BadData,
key + " is required but not specified"
);
}
return muse::make_ok();
};

Manifest m;
// required
Ret ret = check_required(obj, "uri");
if (!ret) {
return RetVal<Manifest>::make_ret(ret);
}
m.uri = Uri(obj.value("uri").toStdString());

ret = check_required(obj, "type");
if (!ret) {
return RetVal<Manifest>::make_ret(ret);
}
m.type = typeFromString(obj.value("type").toStdString());

ret = check_required(obj, "title");
if (!ret) {
return RetVal<Manifest>::make_ret(ret);
}
m.title = obj.value("title").toString();

// optional
m.description = obj.value("description").toString();
m.category = obj.value("category").toString();
m.thumbnail = obj.value("thumbnail").toStdString();
m.version = obj.value("version").toString();
m.apiversion = obj.value("apiversion", DEFAULT_API_VERSION).toInt();

String uiCtx = obj.value("ui_context", String(DEFAULT_UI_CONTEXT)).toString();
if (obj.contains("actions")) {
JsonArray arr = obj.value("actions").toArray();
for (size_t i = 0; i < arr.size(); ++i) {
JsonObject ao = arr.at(i).toObject();
Action a;
a.code = ao.value("code").toStdString();
a.type = ao.contains("type") ? typeFromString(ao.value("type").toStdString()) : m.type;
a.modal = ao.value("modal", DEFAULT_MODAL).toBool();
a.title = ao.value("title").toString();
std::string icon = ao.value("icon").toStdString();
a.icon = ui::IconCode::fromString(icon.c_str());
a.uiCtx = ao.value("ui_context", uiCtx).toString();
a.showOnAppmenu = ao.value("show_on_appmenu", true).toBool();
a.showOnToolbar = ao.value("show_on_toolbar", false).toBool();
a.path = ao.value("path").toStdString();
a.func = ao.value("func", "main").toString();
a.apiversion = m.apiversion;
m.actions.push_back(std::move(a));
}
} else {
//! NOTE Default for actions
const String uiCtx = obj.value("ui_context", String(DEFAULT_UI_CONTEXT)).toString();
const bool modal = obj.value("modal", DEFAULT_MODAL).toBool();

// actions (required)
ret = check_required(obj, "actions");
if (!ret) {
return RetVal<Manifest>::make_ret(ret);
}

const JsonArray arr = obj.value("actions").toArray();
for (size_t i = 0; i < arr.size(); ++i) {
const JsonObject ao = arr.at(i).toObject();
Action a;
a.code = "main";
a.type = m.type;
a.modal = obj.value("modal", DEFAULT_MODAL).toBool();
a.title = m.title;
a.uiCtx = uiCtx;
a.path = obj.value("path").toStdString();
a.func = u"main";
// required
a.path = ao.value("path").toStdString();
if (a.path.empty()) {
return RetVal<Manifest>::make_ret(Ret::Code::BadData,
"action `path` is required but not specified"
);
}

// optional
a.code = ao.value("code", String::number(i)).toStdString();
a.type = ao.contains("type") ? typeFromString(ao.value("type").toStdString()) : m.type;
a.modal = ao.value("modal", modal).toBool();
a.title = ao.value("title").toString();
std::string icon = ao.value("icon").toStdString();
a.icon = ui::IconCode::fromString(icon.c_str());
a.uiCtx = ao.value("ui_context", uiCtx).toString();
a.showOnAppmenu = ao.value("show_on_appmenu", true).toBool();
a.showOnToolbar = ao.value("show_on_toolbar", false).toBool();
a.func = ao.value("func", "main").toString();
a.apiversion = m.apiversion;
m.actions.push_back(std::move(a));
}

return m;
return RetVal<Manifest>::make_ok(m);
}

void ExtensionsLoader::resolvePaths(Manifest& m, const io::path_t& rootDirPath) const
Expand Down
Loading
Loading