Skip to content

Commit

Permalink
get angularjs ui extensions fully working
Browse files Browse the repository at this point in the history
  • Loading branch information
ebiggz committed Nov 22, 2024
1 parent 63c061f commit 2fd99f5
Show file tree
Hide file tree
Showing 11 changed files with 179 additions and 35 deletions.
4 changes: 3 additions & 1 deletion src/backend/ui-extensions/extension-types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ export type BasePage = {
id: string;
name: string;
icon: `fa-${string}`;
fullPage?: boolean;
disableScroll?: boolean;
}

export type AngularJsPage = BasePage & {
Expand All @@ -25,7 +27,7 @@ export type AngularJsFactory = {

export type AngularJsComponent = {
name: string;
binding: Record<string, string>;
bindings: Record<string, string>;
template: string;
transclude?: boolean | string | {[slot: string]: string};
// eslint-disable-next-line @typescript-eslint/ban-types
Expand Down
4 changes: 3 additions & 1 deletion src/backend/ui-extensions/ui-extension-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ class UIExtensionManager {
icon: page.icon,
type: page.type,
template: page.template,
fullPage: page.fullPage,
disableScroll: page.disableScroll,
controllerRaw: page.controller?.toString()
})),
providers: extension.providers
Expand All @@ -45,7 +47,7 @@ class UIExtensionManager {
})),
components: extension.providers.components?.map(component => ({
name: component.name,
binding: component.binding,
bindings: component.bindings,
template: component.template,
transclude: component.transclude,
controllerRaw: component.controller?.toString()
Expand Down
9 changes: 8 additions & 1 deletion src/gui/app/app-main.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,14 @@
'color.picker',
'ngAria',
'ui.codemirror'
]);
], function($controllerProvider, $compileProvider, $provide, $filterProvider) {
global.ngProviders = {
$controllerProvider: $controllerProvider,
$compileProvider: $compileProvider,
$provide: $provide,
$filterProvider: $filterProvider
};
});

app.factory("$exceptionHandler", function(logger) {
// this catches angular exceptions so we can send it to winston
Expand Down
9 changes: 9 additions & 0 deletions src/gui/app/controllers/extension-page.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"use strict";

(function() {
angular
.module("firebotApp")
.controller("extensionPageController", function($scope, $routeParams, uiExtensionsService) {
$scope.page = uiExtensionsService.getPage($routeParams.extensionId, $routeParams.pageId);
});
}());
38 changes: 38 additions & 0 deletions src/gui/app/directives/extension-page-content.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"use strict";
(function() {
angular
.module("firebotApp")
.directive("extensionPageContent", function($compile) {
return {
restrict: "E",
scope: {
page: "="
},
replace: true,
template: `<div></div>`,
compile: function() {
return {
pre: function($scope, element) {
const page = $scope.page;

if (page == null) {
return;
}

const templateString = page.template || "";
const el = angular.element(templateString);

const compiledTemplate = $compile(el)($scope);

element.replaceWith(compiledTemplate);
}
};
},
controller: ($scope, $injector) => {
if ($scope.page?.controller) {
$injector.invoke($scope.page.controller, {}, { $scope: $scope });
}
}
};
});
}());
17 changes: 12 additions & 5 deletions src/gui/app/directives/sidebar/navLink.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@
page: "@",
icon: "@",
isIndex: "<",
badgeText: "<"
badgeText: "<",
customHref: "@?",
extensionId: "<?",
extensionPageId: "<?"
},
template: `
<li>
<a draggable=false class="fb-nav-link" href="{{$ctrl.href}}" ng-class="{'selected': $ctrl.sbm.tabIsSelected($ctrl.page)}" ng-click="$ctrl.sbm.setTab($ctrl.page, $ctrl.name)" uib-tooltip="{{!$ctrl.sbm.navExpanded ? $ctrl.name : ''}}" tooltip-placement="right" tooltip-append-to-body="true">
<a draggable=false class="fb-nav-link" href="{{$ctrl.href}}" ng-class="{'selected': $ctrl.sbm.tabIsSelected($ctrl.page)}" ng-click="$ctrl.sbm.setTab($ctrl.page, $ctrl.name, $ctrl.extensionId, $ctrl.extensionPageId)" uib-tooltip="{{!$ctrl.sbm.navExpanded ? $ctrl.name : ''}}" tooltip-placement="right" tooltip-append-to-body="true">
<div class="nav-link-bar"></div>
<div class="nav-link-icon">
<span class="nav-icon-wrapper">
Expand All @@ -31,9 +34,13 @@
ctrl.sbm = sidebarManager;

ctrl.$onInit = function() {
ctrl.href = ctrl.isIndex
? "#"
: `#!${ctrl.page.toLowerCase().replace(/\W/g, "-")}`;
if (!ctrl.customHref?.length) {
ctrl.href = ctrl.isIndex
? "#"
: `#!${ctrl.page.toLowerCase().replace(/\W/g, "-")}`;
} else {
ctrl.href = `#!${ctrl.customHref}`;
}
};

ctrl.hasBadge = function () {
Expand Down
7 changes: 5 additions & 2 deletions src/gui/app/directives/sidebar/sidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
<div ng-if="$ctrl.extensionPages().length">
<nav-category name="Extensions" pad-top="true"></nav-category>
<nav-link ng-repeat="page in $ctrl.extensionPages()" page="extension.{{page.id}}" name="{{ page.name }}" icon="{{page.icon}}"></nav-link>
<nav-link ng-repeat="page in $ctrl.extensionPages()" extension-id="page.extensionId" extension-page-id="page.id" custom-href="{{page.href}}" page="{{page.href}}" name="{{ page.name }}" icon="{{page.icon}}"></nav-link>
</div>
<nav-category name="{{'SIDEBAR.MANAGEMENT' | translate }}" pad-top="true"></nav-category>
Expand Down Expand Up @@ -127,7 +127,10 @@

ctrl.isViewerDBOn = settingsService.getViewerDB;

ctrl.extensionPages = () => uiExtensionsService.extensions.map(e => e.pages).flat();
ctrl.extensionPages = () => uiExtensionsService.extensions.map(e => e.pages.map(p => {
p.extensionId = e.id;
return p;
})).flat();

ctrl.showConnectionPanelModal = function() {
utilityService.showModal({
Expand Down
4 changes: 2 additions & 2 deletions src/gui/app/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -448,11 +448,11 @@
</div>
<div
class="tab-body-wrapper"
ng-class="{ 'no-scroll': sbm.currentTabShouldntScroll() }"
ng-class="{ 'no-scroll': sbm.disableScroll }"
>
<div
class="tab-body"
ng-class="{ 'fullpage-tab': sbm.currentTabIsFullScreen() }"
ng-class="{ 'fullpage-tab': sbm.fullPage }"
role="main"
>
<!-- angular templating -->
Expand Down
29 changes: 25 additions & 4 deletions src/gui/app/services/sidebar-manager.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
(function() {
angular
.module("firebotApp")
.factory("sidebarManager", function($timeout, $rootScope, settingsService) {
.factory("sidebarManager", function($timeout, $rootScope, settingsService, uiExtensionsService) {
const service = {};

service.navExpanded = settingsService.getSidebarExpanded();
Expand All @@ -17,11 +17,17 @@
service.currentTab = "chat feed";
service.currentTabName = "Dashboard";

service.setTab = function(tabId, name) {
service.fullPage = true;
service.disableScroll = true;

service.setTab = function(tabId, name, extensionId, extensionPageId) {
service.currentTab = tabId.toLowerCase();

service.currentTabName = name ? name : tabId;

service.fullPage = service.currentTabIsFullScreen(extensionId, extensionPageId);
service.disableScroll = service.currentTabShouldntScroll(extensionId, extensionPageId);

//hack that somewhat helps with the autoupdate slider styling issues on first load
$timeout(function() {
$rootScope.$broadcast("rzSliderForceRender");
Expand All @@ -32,7 +38,12 @@
return service.currentTab.toLowerCase() === tabId.toLowerCase();
};

service.currentTabIsFullScreen = function() {
service.currentTabIsFullScreen = function(extensionId, extensionPageId) {
const isExtensionPage = extensionId != null;
if (isExtensionPage) {
const page = uiExtensionsService.getPage(extensionId, extensionPageId);
return page?.fullPage ?? false;
}
return [
"chat feed",
"commands",
Expand All @@ -55,7 +66,12 @@
].includes(service.currentTab.toLowerCase());
};

service.currentTabShouldntScroll = function() {
service.currentTabShouldntScroll = function(extensionId, extensionPageId) {
const isExtensionPage = extensionId != null;
if (isExtensionPage) {
const page = uiExtensionsService.getPage(extensionId, extensionPageId);
return page?.disableScroll ?? false;
}
return [
"chat feed",
"commands",
Expand Down Expand Up @@ -174,6 +190,11 @@
.when("/variable-macros", {
templateUrl: "./templates/_variable-macros.html",
controller: "variableMacrosController"
})

.when("/extension/:extensionId/:pageId", {
templateUrl: "./templates/_extension-page.html",
controller: "extensionPageController"
});
}
]);
Expand Down
89 changes: 70 additions & 19 deletions src/gui/app/services/ui-extensions.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,34 @@
backendCommunicator.send("ui-extensions-ready");
};

service.getPage = (extensionId, pageId) => {
const extension = service.extensions.find(e => e.id === extensionId);
if (!extension) {
return null;
}
const page = extension.pages?.find(p => p.id === pageId);
return page;
};

function parseRawExtension(extension) {
const normalizedExtensionId = extension.id.toLowerCase().replace(/ /, "-");
return {
id: extension.id,
pages: extension.pages?.map(page => ({
id: page.id,
name: page.name,
icon: page.icon,
type: page.type,
template: page.template,
// eslint-disable-next-line no-eval
controller: page.controllerRaw ? eval(page.controllerRaw) : undefined
})),
id: normalizedExtensionId,
pages: extension.pages?.map((page) => {
const normalizedPageId = page.id.toLowerCase().replace(/ /, "-");
return {
id: normalizedPageId,
name: page.name,
href: `extension/${normalizedExtensionId}/${normalizedPageId}`,
icon: page.icon,
type: page.type,
fullPage: page.fullPage,
disableScroll: page.disableScroll,
template: page.template,
// eslint-disable-next-line no-eval
controller: page.controllerRaw ? eval(page.controllerRaw) : undefined
};
}),
providers: extension.providers
? {
factories: extension.providers.factories?.map(factory => ({
Expand All @@ -33,7 +49,7 @@
})),
components: extension.providers.components?.map(component => ({
name: component.name,
binding: component.binding,
bindings: component.bindings,
template: component.template,
transclude: component.transclude,
// eslint-disable-next-line no-eval
Expand All @@ -54,20 +70,55 @@
};
}

backendCommunicator.on("all-ui-extensions", (extensions) => {
const parsedExtensions = extensions.map(parseRawExtension);
function installExtension(extension) {
const parsedExtension = parseRawExtension(extension);
service.extensions = [
...service.extensions,
...parsedExtensions
parsedExtension
];

if (parsedExtension.providers) {
parsedExtension.providers.factories?.forEach((factory) => {
angular.module("firebotApp").factory(factory.name, factory.function);
// eslint-disable-next-line no-undef
ngProviders?.$provide.factory(factory.name, factory.function);
});
parsedExtension.providers.components?.forEach((component) => {
angular.module("firebotApp").component(component.name, {
bindings: component.bindings,
template: component.template,
transclude: component.transclude,
controller: component.controller
});
// eslint-disable-next-line no-undef
ngProviders?.$compileProvider.component(component.name, {
bindings: component.bindings,
template: component.template,
transclude: component.transclude,
controller: component.controller
});
});
parsedExtension.providers.directives?.forEach((directive) => {
angular.module("firebotApp").directive(directive.name, directive.function);
// eslint-disable-next-line no-undef
ngProviders?.$compileProvider.directive(directive.name, directive.function);
});
parsedExtension.providers.filters?.forEach((filter) => {
angular.module("firebotApp").filter(filter.name, filter.function);
// eslint-disable-next-line no-undef
ngProviders?.$filterProvider.register(filter.name, filter.function);
});
}
}

backendCommunicator.on("all-ui-extensions", (extensions) => {
for (const extension of extensions) {
installExtension(extension);
}
});

backendCommunicator.on("ui-extension-registered", (extension) => {
const parsedExtension = parseRawExtension(extension);
service.extensions = [
...service.extensions,
parsedExtension
];
installExtension(extension);
});

return service;
Expand Down
4 changes: 4 additions & 0 deletions src/gui/app/templates/_extension-page.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<extension-page-content ng-if="page && page.type === 'angularjs'" page="page"></extension-page-content>

<!-- Below line is just an example, actual iframe implementation TBD -->
<!-- <iframe ng-if="page && page.type === 'iframe'" ng-src="{{page.url}}" frameborder="0" width="100%" height="100%"></iframe> -->

0 comments on commit 2fd99f5

Please sign in to comment.