Nuke popup iFrames

This commit is contained in:
Katharina Bogad 2021-06-24 18:56:36 +02:00
parent 29f1119c16
commit 7a7e944c09
15 changed files with 113 additions and 124 deletions

View File

@ -197,7 +197,7 @@ class BaseController
return $this->View->render($response, $page, $data);
}
protected function renderPage($response, $page, $data = [])
protected function renderPage($request, $response, $page, $data = [])
{
$this->View->set('userentitiesForSidebar', $this->getDatabase()->userentities()->where('show_in_sidebar_menu = 1')->orderBy('name'));
try
@ -217,7 +217,7 @@ class BaseController
// Happens when database is not initialised or migrated...
}
return $this->render($response, $page, $data);
return $this->render($request, $response, $page, $data);
}
private static $htmlPurifierInstance = null;

View File

@ -68,7 +68,7 @@ class OpenApiController extends BaseApiController
public function DocumentationUi(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
return $this->render($response, 'openapiui');
return $this->render($request, $response, 'openapiui');
}
public function __construct(\DI\Container $container)

View File

@ -88,7 +88,6 @@ view_eslint_config.globals = eslint_config.globals.concat([
// viewjs handling
var files = glob.sync('./js/viewjs/*.js');
var components = glob.sync('./js/viewjs/components/*.js');
var viewJStasks = [];
@ -111,32 +110,10 @@ files.forEach(function(target)
.pipe(gulpif(minify, uglify()))
.pipe(buffer())
.pipe(sourcemaps.init({ loadMaps: true }))
.pipe(sourcemaps.write('.'))
.pipe(sourcemaps.write('.', { sourceMappingURLPrefix: '/viewjs' }))
.pipe(dest('./public/viewjs')));
viewJStasks.push(target);
});
components.forEach(function(target)
{
task(target, cb => rollup({
input: target,
output: {
format: 'umd',
name: path.basename(target),
sourcemap: 'inline',
},
plugins: [resolve(), rollupCss({
dest: path.resolve('./public/css/viewcss/' + path.basename(target).replace(".js", ".css")),
}), commonjs(), eslint(view_eslint_config)],
})
.pipe(source(path.basename(target), "./js/viewjs/components"))
.pipe(buffer())
.pipe(gulpif(minify, uglify()))
.pipe(buffer())
.pipe(sourcemaps.init({ loadMaps: true }))
.pipe(sourcemaps.write('.'))
.pipe(dest('./public/viewjs/components')));
viewJStasks.push(target);
});
// The `clean` function is not exported so it can be considered a private task.
// It can still be used within the `series()` composition.

View File

@ -107,6 +107,21 @@ class GrocyClass
}
});
window.addEventListener('load', function()
{
if (self.documentReady) return;
// preload views
self.documentReady = true;
var element = self.preloadViews.pop();
while (element !== undefined)
{
self.PreloadView(element.viewName, element.loadCss, element.cb);
element = self.preloadViews.pop();
}
});
// save the config
this.config = config;
@ -118,18 +133,6 @@ class GrocyClass
}
});
}
$(document).on('ready', () =>
{
this.documentReady = true;
var element = self.preloadViews.pop();
while (element !== undefined)
{
self.preloadViews(element.viewName, element.loadCss, element.cb);
element = self.preloadViews.pop();
}
})
}
static createSingleton(config, view)
@ -280,7 +283,7 @@ class GrocyClass
{
if (!this.documentReady)
{
this.preloadViews.push({ viewName: viewName, loadCss: loadCss, cb: cb });
this.preloadViews.push({ viewName: viewName, loadCss: loadCSS, cb: cb });
return;
}
@ -297,7 +300,7 @@ class GrocyClass
$("<link/>", {
rel: "stylesheet",
type: "text/css",
href: this.FormatUrl('/css/viewcss/' + viewName + '.cs')
href: this.FormatUrl('/css/viewcss/' + viewName + '.css')
}).appendTo("head");
}
}
@ -310,16 +313,51 @@ class GrocyClass
OpenSubView(link)
{
var self = this;
console.log("loading subview " + link);
$.ajax({
dataType: "json",
link,
url: link,
success: (data) =>
{
let scopeId = uuid.v4()
var proxy = new GrocyProxy(this, "#" + scopeId, data.config, link);
var grocyProxy = new GrocyProxy(this, "#" + scopeId, data.config, link);
var proxy = new Proxy(grocyProxy, {
get: function(target, prop, receiver)
{
if (prop in grocyProxy)
{
return grocyProxy[prop];
}
else
{
return self[prop];
}
},
apply: function(target, thisArg, args)
{
if (target in grocyProxy)
{
return grocyProxy[target](...args);
}
else
{
return self[target](...args);
}
},
ownKeys: function(oTarget, sKey)
{
let root = Reflect.ownKeys(self);
Array.concat(root, Reflect.ownKeys(grocyProxy));
return root;
},
has: function(oTarget, sKey)
{
return sKey in self || sKey in grocyProxy;
},
});
bootbox.dialog({
message: '<div id="' + scopeId + '">' + data.template + '</div>',
message: '<div class="embedded" id="' + scopeId + '">' + data.template + '</div>',
size: 'large',
backdrop: true,
closeButton: false,
@ -329,7 +367,6 @@ class GrocyClass
className: 'btn-secondary responsive-button',
callback: function()
{
proxy.Unload();
bootbox.hideAll();
}
}
@ -338,10 +375,12 @@ class GrocyClass
{
// dialog div is alive, init view.
// this occurs before the view is shown.
grocyProxy.Initialize(proxy);
self.LoadView(data.viewJsName, "#" + scopeId, proxy);
}
});
}
},
error: (xhr, text, data) => { console.error(text); }
})

View File

@ -5,11 +5,11 @@ class GrocyFrontendHelpers
{
this.Grocy = Grocy;
this.Api = Api;
var self = this;
if (scope != null)
{
this.scope = $(scope);
this.$scope = (selector) => self.scope.find(selector);
var jScope = this.scope;
this.$scope = (selector) => jScope.find(selector);
this.scopeSelector = scope;
}
else

View File

@ -4,14 +4,13 @@ import * as components from '../components';
class GrocyProxy
{
constructor(RootGrocy, scopeSelector, config, url)
{
this.RootGrocy = RootGrocy;
// proxy-local members, because they might not be set globally.
this.QuantityUnits = config.QuantityUnits;
this.QuantityUnitConversionsResolved = config.QuantityUnitConversionsResolved || [];
this.QuantityUnitConversionsResolved = config.QuantityUnitConversionsResolved;
this.QuantityUnitEditFormRedirectUri = config.QuantityUnitEditFormRedirectUri;
this.MealPlanFirstDayOfWeek = config.MealPlanFirstDayOfWeek;
this.EditMode = config.EditMode;
@ -31,9 +30,9 @@ class GrocyProxy
// scoping
this.scopeSelector = scopeSelector;
this.scope = $(scopeSelector);
var jScope = this.scope;
this.$scope = (selector) => jScope.find(selector);
this.scope = null;
this.$scope = null;
var queryString = url.split('?', 2);
this.virtualUrl = queryString.length == 2 ? queryString[1] : ""; // maximum two parts
@ -45,52 +44,19 @@ class GrocyProxy
Object.assign(this.config.UserSettings, RootGrocy.UserSettings);
this.UserSettings = config.UserSettings;
}
this.configProxy = Proxy.revocable(this.config, {
get: function(target, prop, receiver)
{
if (Object.prototype.hasOwnProperty.call(target, prop))
{
return Reflect.get(...arguments);
}
else
{
return Reflect.get(RootGrocy.config, prop, target);
}
}
})
// This is where the magic happens!
// basically, this Proxy object checks if a member is defined in this proxy class,
// and returns it if so.
// If not, the prop is handed over to the root grocy instance.
this.grocyProxy = Proxy.revocable(this, {
get: function(target, prop, receiver)
{
if (Object.prototype.hasOwnProperty.call(target, prop))
{
return Reflect.get(...arguments)
}
else
{
return Reflect.get(RootGrocy, prop, receiver);
}
}
});
// scoped variants of some helpers
this.FrontendHelpers = new GrocyFrontendHelpers(this, RootGrocy.Api, this.scopeSelector);
}
Unload()
Initialize(proxy)
{
this.grocyProxy.revoke();
this.configProxy.revoke();
this.scope = $(this.scopeSelector);
var jScope = this.scope;
this.$scope = (selector) => jScope.find(selector);
this.FrontendHelpers = new GrocyFrontendHelpers(proxy, this.RootGrocy.Api, this.scopeSelector);
}
Use(componentName, scope = null)
{
let scopeName = scope || "";
let scopeName = scope || this.scopeSelector;
// initialize Components only once per scope
if (this.initComponents.find(elem => elem == componentName + scopeName))
return this.Components[componentName + scopeName];
@ -109,14 +75,6 @@ class GrocyProxy
}
}
LoadView(viewName)
{
if (Object.prototype.hasOwnProperty.call(window, viewName + "View"))
{
window[viewName + "View"](this, this.scopeSelector);
}
}
// URI params on integrated components don't work because they
// don't have an URL. So let's fake it.
GetUriParam(key)

View File

@ -7,10 +7,8 @@
}
// preload some views.
top.on('load', () =>
{
Grocy.PreloadView("batteryform");
});
Grocy.PreloadView("batteryform");
var batteriesTable = $scope('#batteries-table').DataTable({
'order': [[1, 'asc']],

View File

@ -9,11 +9,9 @@
var batterycard = Grocy.Use("batterycard");
// preload some views.
top.on('load', () =>
{
Grocy.PreloadView("batteriesjournal");
Grocy.PreloadView("batteryform");
});
Grocy.PreloadView("batteriesjournal");
Grocy.PreloadView("batteryform");
var batteriesOverviewTable = $scope('#batteries-overview-table').DataTable({
'order': [[4, 'asc']],

View File

@ -11,10 +11,8 @@
var chorecard = Grocy.Use("chorecard");
// preload some views.
top.on('load', () =>
{
Grocy.PreloadView("choresjournal");
});
Grocy.PreloadView("choresjournal");
var choresOverviewTable = $scope('#chores-overview-table').DataTable({
'order': [[2, 'asc']],

View File

@ -7,10 +7,7 @@
}
// preload some views.
top.on('load', () =>
{
Grocy.PreloadView("locationform");
});
Grocy.PreloadView("locationform");
var locationsTable = $scope('#locations-table').DataTable({
'order': [[1, 'asc']],

View File

@ -13,10 +13,7 @@ function productformView(Grocy, scope = null)
Grocy.Use("numberpicker");
// preload some views.
top.on('load', () =>
{
Grocy.PreloadView("productgroupform");
});
Grocy.PreloadView("productgroupform");
var shoppinglocationpicker = Grocy.Use("shoppinglocationpicker");
var userfields = Grocy.Use("userfieldsform");

View File

@ -15,7 +15,7 @@
Grocy.PreloadView("stockentryform");
Grocy.PreloadView("shoppinglistitemform");
Grocy.PreloadView("purchase");
Grocy.PreloadView("conusme");
Grocy.PreloadView("consume");
Grocy.PreloadView("inventory");
Grocy.PreloadView("stockjournal");
Grocy.PreloadView("stockjournalsummary");

View File

@ -14,7 +14,7 @@
Grocy.PreloadView("stockentries");
Grocy.PreloadView("shoppinglistitemform");
Grocy.PreloadView("purchase");
Grocy.PreloadView("conusme");
Grocy.PreloadView("consume");
Grocy.PreloadView("inventory");
Grocy.PreloadView("stockjournal");
Grocy.PreloadView("stockjournalsummary");

View File

@ -1,5 +1,28 @@
@php
// this is a shoe-horned method to generate a "proper" subview as JSON.
$content = $__env->yieldContent('content');
// Javascript is not JSON, so we need to do some trickery
// to reencode stuff like additional , at the end or
// ' instead of " as field delimter.
$config = "{\n" . $__env->yieldContent('grocyConfigProps') . '}';
$config = preg_replace('/(\n[\t ]*)([a-zA-Z0-9]+):/','${1}"${2}":', $config);
$config = preg_replace('/: *\'(.*?)\',?\n/', ':"${1}",', $config);
$config = preg_replace('/,}$/', '}', $config);
$grocy_options = json_decode($config, true);
$usersettings = $__env->yieldContent('forceUserSettings');
$usersettings = json_decode('{' . $usersettings . '}');
if($usersettings != null)
$grocy_options["UserSettings"] = $usersettings;
// worst case this burns on the front end.
$viewname = trim($__env->yieldContent('viewJsName'));
echo json_encode([
'template' => $content,
'config' => $grocy_options,
'viewJsName' => $viewname,
]);
@endphp

View File

@ -17,8 +17,12 @@
@endsection
@section('content')
@php
$classes = $embedded ? '' : 'col-md-6 col-xl-4';
@endphp
<div class="row">
<div class="col-xs-12 col-md-6 col-xl-4 pb-3">
<div class="col-12 {{ $classes }} pb-3">
<div class="title-related-links">
<h2 class="title">@yield('title')</h2>
<button class="btn btn-outline-dark d-md-none mt-2 float-right order-1 order-md-3 hide-when-embedded"