mirror of
https://github.com/pretix/pretix.git
synced 2026-05-11 16:13:59 +00:00
migrate checkin rules editor to vue3
- move constants to a module - move reading from and writing to non-vue html to django interop module - switch to composition api and script setup sfc with pug - use optional chaining operators a lot to simplify code
This commit is contained in:
5
.editorconfig
Normal file
5
.editorconfig
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
[*.{js,jsx,ts,tsx,vue}]
|
||||||
|
indent_style = tab
|
||||||
|
indent_size = 2
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
26
package-lock.json
generated
26
package-lock.json
generated
@@ -14,6 +14,8 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.39.2",
|
"@eslint/js": "^9.39.2",
|
||||||
"@stylistic/eslint-plugin": "^5.7.1",
|
"@stylistic/eslint-plugin": "^5.7.1",
|
||||||
|
"@types/jquery": "^3.5.33",
|
||||||
|
"@types/moment": "^2.11.29",
|
||||||
"@vitejs/plugin-vue": "^6.0.4",
|
"@vitejs/plugin-vue": "^6.0.4",
|
||||||
"@vue/eslint-config-typescript": "^14.6.0",
|
"@vue/eslint-config-typescript": "^14.6.0",
|
||||||
"eslint": "^9.39.2",
|
"eslint": "^9.39.2",
|
||||||
@@ -1223,6 +1225,16 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/jquery": {
|
||||||
|
"version": "3.5.33",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.33.tgz",
|
||||||
|
"integrity": "sha512-SeyVJXlCZpEki5F0ghuYe+L+PprQta6nRZqhONt9F13dWBtR/ftoaIbdRQ7cis7womE+X2LKhsDdDtkkDhJS6g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/sizzle": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/json-schema": {
|
"node_modules/@types/json-schema": {
|
||||||
"version": "7.0.15",
|
"version": "7.0.15",
|
||||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
||||||
@@ -1230,6 +1242,20 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/moment": {
|
||||||
|
"version": "2.11.29",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/moment/-/moment-2.11.29.tgz",
|
||||||
|
"integrity": "sha512-D5WIgbLYQzvgfsDnBhZFSTnt/BjGPOE+Jsh3k1BYYijJAkrn7ceeLvU4jtjKKXXuXN42O3ARlU4D/P9ezbQYFA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/sizzle": {
|
||||||
|
"version": "2.3.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.10.tgz",
|
||||||
|
"integrity": "sha512-TC0dmN0K8YcWEAEfiPi5gJP14eJe30TTGjkvek3iM/1NdHHsdCA/Td6GvNndMOo/iSnIsZ4HuuhrYPDAmbxzww==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||||
"version": "8.54.0",
|
"version": "8.54.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.54.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.54.0.tgz",
|
||||||
|
|||||||
@@ -29,6 +29,8 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.39.2",
|
"@eslint/js": "^9.39.2",
|
||||||
"@stylistic/eslint-plugin": "^5.7.1",
|
"@stylistic/eslint-plugin": "^5.7.1",
|
||||||
|
"@types/jquery": "^3.5.33",
|
||||||
|
"@types/moment": "^2.11.29",
|
||||||
"@vitejs/plugin-vue": "^6.0.4",
|
"@vitejs/plugin-vue": "^6.0.4",
|
||||||
"@vue/eslint-config-typescript": "^14.6.0",
|
"@vue/eslint-config-typescript": "^14.6.0",
|
||||||
"eslint": "^9.39.2",
|
"eslint": "^9.39.2",
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
{% load bootstrap3 %}
|
{% load bootstrap3 %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% load compress %}
|
{% load compress %}
|
||||||
|
{% load vite %}
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{% if checkinlist %}
|
{% if checkinlist %}
|
||||||
{% blocktrans with name=checkinlist.name %}Check-in list: {{ name }}{% endblocktrans %}
|
{% blocktrans with name=checkinlist.name %}Check-in list: {{ name }}{% endblocktrans %}
|
||||||
@@ -74,45 +75,8 @@
|
|||||||
{% bootstrap_field form.ignore_in_statistics layout="control" %}
|
{% bootstrap_field form.ignore_in_statistics layout="control" %}
|
||||||
|
|
||||||
<h3>{% trans "Custom check-in rule" %}</h3>
|
<h3>{% trans "Custom check-in rule" %}</h3>
|
||||||
<div id="rules-editor" class="form-inline">
|
<div id="rules-editor">
|
||||||
<div>
|
<!-- Vue app mount point -->
|
||||||
<ul class="nav nav-tabs" role="tablist">
|
|
||||||
<li role="presentation" class="active">
|
|
||||||
<a href="#rules-edit" role="tab" data-toggle="tab">
|
|
||||||
<span class="fa fa-edit"></span>
|
|
||||||
{% trans "Edit" %}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li role="presentation">
|
|
||||||
<a href="#rules-viz" role="tab" data-toggle="tab">
|
|
||||||
<span class="fa fa-eye"></span>
|
|
||||||
{% trans "Visualize" %}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<!-- Tab panes -->
|
|
||||||
<div class="tab-content">
|
|
||||||
<div role="tabpanel" class="tab-pane active" id="rules-edit">
|
|
||||||
<checkin-rules-editor></checkin-rules-editor>
|
|
||||||
</div>
|
|
||||||
<div role="tabpanel" class="tab-pane" id="rules-viz">
|
|
||||||
<checkin-rules-visualization></checkin-rules-visualization>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="alert alert-info" v-if="missingItems.length">
|
|
||||||
<p>
|
|
||||||
{% trans "Your rule always filters by product or variation, but the following products or variations are not contained in any of your rule parts so people with these tickets will not get in:" %}
|
|
||||||
</p>
|
|
||||||
<ul>
|
|
||||||
<li v-for="h in missingItems">{{ "{" }}{h}{{ "}" }}</li>
|
|
||||||
</ul>
|
|
||||||
<p>
|
|
||||||
{% trans "Please double-check if this was intentional." %}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="disabled-withoutjs sr-only">
|
<div class="disabled-withoutjs sr-only">
|
||||||
{{ form.rules }}
|
{{ form.rules }}
|
||||||
@@ -127,11 +91,6 @@
|
|||||||
</form>
|
</form>
|
||||||
{{ items|json_script:"items" }}
|
{{ items|json_script:"items" }}
|
||||||
|
|
||||||
{% if DEBUG %}
|
|
||||||
<script type="text/javascript" src="{% static "vuejs/vue.js" %}"></script>
|
|
||||||
{% else %}
|
|
||||||
<script type="text/javascript" src="{% static "vuejs/vue.min.js" %}"></script>
|
|
||||||
{% endif %}
|
|
||||||
{% compress js %}
|
{% compress js %}
|
||||||
<script type="text/javascript" src="{% static "d3/d3.v6.js" %}"></script>
|
<script type="text/javascript" src="{% static "d3/d3.v6.js" %}"></script>
|
||||||
<script type="text/javascript" src="{% static "d3/d3-color.v2.js" %}"></script>
|
<script type="text/javascript" src="{% static "d3/d3-color.v2.js" %}"></script>
|
||||||
@@ -144,15 +103,6 @@
|
|||||||
<script type="text/javascript" src="{% static "d3/d3-drag.v2.js" %}"></script>
|
<script type="text/javascript" src="{% static "d3/d3-drag.v2.js" %}"></script>
|
||||||
<script type="text/javascript" src="{% static "d3/d3-zoom.v2.js" %}"></script>
|
<script type="text/javascript" src="{% static "d3/d3-zoom.v2.js" %}"></script>
|
||||||
{% endcompress %}
|
{% endcompress %}
|
||||||
{% compress js %}
|
{% vite_hmr %}
|
||||||
<script type="text/javascript" src="{% static "pretixcontrol/js/ui/checkinrules/jsonlogic-boolalg.js" %}"></script>
|
{% vite_asset "src/pretix/static/pretixcontrol/js/ui/checkinrules/" %}
|
||||||
<script type="text/vue" src="{% static 'pretixcontrol/js/ui/checkinrules/datetimefield.vue' %}"></script>
|
|
||||||
<script type="text/vue" src="{% static 'pretixcontrol/js/ui/checkinrules/timefield.vue' %}"></script>
|
|
||||||
<script type="text/vue" src="{% static 'pretixcontrol/js/ui/checkinrules/lookup-select2.vue' %}"></script>
|
|
||||||
<script type="text/vue" src="{% static 'pretixcontrol/js/ui/checkinrules/checkin-rule.vue' %}"></script>
|
|
||||||
<script type="text/vue" src="{% static 'pretixcontrol/js/ui/checkinrules/checkin-rules-editor.vue' %}"></script>
|
|
||||||
<script type="text/vue" src="{% static 'pretixcontrol/js/ui/checkinrules/viz-node.vue' %}"></script>
|
|
||||||
<script type="text/vue" src="{% static 'pretixcontrol/js/ui/checkinrules/checkin-rules-visualization.vue' %}"></script>
|
|
||||||
<script type="text/javascript" src="{% static "pretixcontrol/js/ui/checkinrules.js" %}"></script>
|
|
||||||
{% endcompress %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
{% load getitem %}
|
{% load getitem %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% load compress %}
|
{% load compress %}
|
||||||
|
{% load vite %}
|
||||||
{% block title %}{% trans "Check-in simulator" %}{% endblock %}
|
{% block title %}{% trans "Check-in simulator" %}{% endblock %}
|
||||||
{% block inside %}
|
{% block inside %}
|
||||||
<h1>
|
<h1>
|
||||||
@@ -124,11 +125,9 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% if result.rule_graph %}
|
{% if result.rule_graph %}
|
||||||
<div id="rules-editor" class="form-inline">
|
<div id="rules-editor" class="form-inline">
|
||||||
<div role="tabpanel" class="tab-pane" id="rules-viz">
|
<!-- Vue app mount point -->
|
||||||
<checkin-rules-visualization></checkin-rules-visualization>
|
|
||||||
</div>
|
</div>
|
||||||
<textarea id="id_rules" class="sr-only">{{ result.rule_graph|attr_escapejson_dumps }}</textarea>
|
<textarea id="id_rules" class="sr-only">{{ result.rule_graph|attr_escapejson_dumps }}</textarea>
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -152,10 +151,6 @@
|
|||||||
<script type="text/javascript" src="{% static "d3/d3-drag.v2.js" %}"></script>
|
<script type="text/javascript" src="{% static "d3/d3-drag.v2.js" %}"></script>
|
||||||
<script type="text/javascript" src="{% static "d3/d3-zoom.v2.js" %}"></script>
|
<script type="text/javascript" src="{% static "d3/d3-zoom.v2.js" %}"></script>
|
||||||
{% endcompress %}
|
{% endcompress %}
|
||||||
{% compress js %}
|
{% vite_hmr %}
|
||||||
<script type="text/javascript" src="{% static "pretixcontrol/js/ui/checkinrules/jsonlogic-boolalg.js" %}"></script>
|
{% vite_asset "src/pretix/static/pretixcontrol/js/ui/checkinrules/" %}
|
||||||
<script type="text/vue" src="{% static 'pretixcontrol/js/ui/checkinrules/viz-node.vue' %}"></script>
|
|
||||||
<script type="text/vue" src="{% static 'pretixcontrol/js/ui/checkinrules/checkin-rules-visualization.vue' %}"></script>
|
|
||||||
<script type="text/javascript" src="{% static "pretixcontrol/js/ui/checkinrules.js" %}"></script>
|
|
||||||
{% endcompress %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -1,318 +0,0 @@
|
|||||||
$(function () {
|
|
||||||
var TYPEOPS = {
|
|
||||||
// Every change to our supported JSON logic must be done
|
|
||||||
// * in pretix.base.services.checkin
|
|
||||||
// * in pretix.base.models.checkin
|
|
||||||
// * in pretix.helpers.jsonlogic_boolalg
|
|
||||||
// * in checkinrules.js
|
|
||||||
// * in libpretixsync
|
|
||||||
// * in pretixscan-ios
|
|
||||||
'product': {
|
|
||||||
'inList': {
|
|
||||||
'label': gettext('is one of'),
|
|
||||||
'cardinality': 2,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'variation': {
|
|
||||||
'inList': {
|
|
||||||
'label': gettext('is one of'),
|
|
||||||
'cardinality': 2,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'gate': {
|
|
||||||
'inList': {
|
|
||||||
'label': gettext('is one of'),
|
|
||||||
'cardinality': 2,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'datetime': {
|
|
||||||
'isBefore': {
|
|
||||||
'label': gettext('is before'),
|
|
||||||
'cardinality': 2,
|
|
||||||
},
|
|
||||||
'isAfter': {
|
|
||||||
'label': gettext('is after'),
|
|
||||||
'cardinality': 2,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'enum_entry_status': {
|
|
||||||
'==': {
|
|
||||||
'label': gettext('='),
|
|
||||||
'cardinality': 2,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'int_by_datetime': {
|
|
||||||
'<': {
|
|
||||||
'label': '<',
|
|
||||||
'cardinality': 2,
|
|
||||||
},
|
|
||||||
'<=': {
|
|
||||||
'label': '≤',
|
|
||||||
'cardinality': 2,
|
|
||||||
},
|
|
||||||
'>': {
|
|
||||||
'label': '>',
|
|
||||||
'cardinality': 2,
|
|
||||||
},
|
|
||||||
'>=': {
|
|
||||||
'label': '≥',
|
|
||||||
'cardinality': 2,
|
|
||||||
},
|
|
||||||
'==': {
|
|
||||||
'label': '=',
|
|
||||||
'cardinality': 2,
|
|
||||||
},
|
|
||||||
'!=': {
|
|
||||||
'label': '≠',
|
|
||||||
'cardinality': 2,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'int': {
|
|
||||||
'<': {
|
|
||||||
'label': '<',
|
|
||||||
'cardinality': 2,
|
|
||||||
},
|
|
||||||
'<=': {
|
|
||||||
'label': '≤',
|
|
||||||
'cardinality': 2,
|
|
||||||
},
|
|
||||||
'>': {
|
|
||||||
'label': '>',
|
|
||||||
'cardinality': 2,
|
|
||||||
},
|
|
||||||
'>=': {
|
|
||||||
'label': '≥',
|
|
||||||
'cardinality': 2,
|
|
||||||
},
|
|
||||||
'==': {
|
|
||||||
'label': '=',
|
|
||||||
'cardinality': 2,
|
|
||||||
},
|
|
||||||
'!=': {
|
|
||||||
'label': '≠',
|
|
||||||
'cardinality': 2,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
var VARS = {
|
|
||||||
'product': {
|
|
||||||
'label': gettext('Product'),
|
|
||||||
'type': 'product',
|
|
||||||
},
|
|
||||||
'variation': {
|
|
||||||
'label': gettext('Product variation'),
|
|
||||||
'type': 'variation',
|
|
||||||
},
|
|
||||||
'gate': {
|
|
||||||
'label': gettext('Gate'),
|
|
||||||
'type': 'gate',
|
|
||||||
},
|
|
||||||
'now': {
|
|
||||||
'label': gettext('Current date and time'),
|
|
||||||
'type': 'datetime',
|
|
||||||
},
|
|
||||||
'now_isoweekday': {
|
|
||||||
'label': gettext('Current day of the week (1 = Monday, 7 = Sunday)'),
|
|
||||||
'type': 'int',
|
|
||||||
},
|
|
||||||
'entry_status': {
|
|
||||||
'label': gettext('Current entry status'),
|
|
||||||
'type': 'enum_entry_status',
|
|
||||||
},
|
|
||||||
'entries_number': {
|
|
||||||
'label': gettext('Number of previous entries'),
|
|
||||||
'type': 'int',
|
|
||||||
},
|
|
||||||
'entries_today': {
|
|
||||||
'label': gettext('Number of previous entries since midnight'),
|
|
||||||
'type': 'int',
|
|
||||||
},
|
|
||||||
'entries_since': {
|
|
||||||
'label': gettext('Number of previous entries since'),
|
|
||||||
'type': 'int_by_datetime',
|
|
||||||
},
|
|
||||||
'entries_before': {
|
|
||||||
'label': gettext('Number of previous entries before'),
|
|
||||||
'type': 'int_by_datetime',
|
|
||||||
},
|
|
||||||
'entries_days': {
|
|
||||||
'label': gettext('Number of days with a previous entry'),
|
|
||||||
'type': 'int',
|
|
||||||
},
|
|
||||||
'entries_days_since': {
|
|
||||||
'label': gettext('Number of days with a previous entry since'),
|
|
||||||
'type': 'int_by_datetime',
|
|
||||||
},
|
|
||||||
'entries_days_before': {
|
|
||||||
'label': gettext('Number of days with a previous entry before'),
|
|
||||||
'type': 'int_by_datetime',
|
|
||||||
},
|
|
||||||
'minutes_since_last_entry': {
|
|
||||||
'label': gettext('Minutes since last entry (-1 on first entry)'),
|
|
||||||
'type': 'int',
|
|
||||||
},
|
|
||||||
'minutes_since_first_entry': {
|
|
||||||
'label': gettext('Minutes since first entry (-1 on first entry)'),
|
|
||||||
'type': 'int',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
var components = {
|
|
||||||
CheckinRulesVisualization: CheckinRulesVisualization.default,
|
|
||||||
}
|
|
||||||
if (typeof CheckinRule !== "undefined") {
|
|
||||||
Vue.component('checkin-rule', CheckinRule.default);
|
|
||||||
components = {
|
|
||||||
CheckinRulesEditor: CheckinRulesEditor.default,
|
|
||||||
CheckinRulesVisualization: CheckinRulesVisualization.default,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var app = new Vue({
|
|
||||||
el: '#rules-editor',
|
|
||||||
components: components,
|
|
||||||
data: function () {
|
|
||||||
return {
|
|
||||||
rules: {},
|
|
||||||
items: [],
|
|
||||||
all_products: false,
|
|
||||||
limit_products: [],
|
|
||||||
TYPEOPS: TYPEOPS,
|
|
||||||
VARS: VARS,
|
|
||||||
texts: {
|
|
||||||
and: gettext('All of the conditions below (AND)'),
|
|
||||||
or: gettext('At least one of the conditions below (OR)'),
|
|
||||||
date_from: gettext('Event start'),
|
|
||||||
date_to: gettext('Event end'),
|
|
||||||
date_admission: gettext('Event admission'),
|
|
||||||
date_custom: gettext('custom date and time'),
|
|
||||||
date_customtime: gettext('custom time'),
|
|
||||||
date_tolerance: gettext('Tolerance (minutes)'),
|
|
||||||
condition_add: gettext('Add condition'),
|
|
||||||
minutes: gettext('minutes'),
|
|
||||||
duplicate: gettext('Duplicate'),
|
|
||||||
status_present: pgettext('entry_status', 'present'),
|
|
||||||
status_absent: pgettext('entry_status', 'absent'),
|
|
||||||
},
|
|
||||||
hasRules: false,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
missingItems: function () {
|
|
||||||
// This computed property contains list of item or variation names that
|
|
||||||
// a) Are allowed on the checkin list according to all_products or include_products
|
|
||||||
// b) Are not matched by ANY logical branch of the rule.
|
|
||||||
// The list will be empty if there is a "catch-all" rule.
|
|
||||||
var products_seen = {};
|
|
||||||
var variations_seen = {};
|
|
||||||
var rules = convert_to_dnf(this.rules);
|
|
||||||
var branch_without_product_filter = false;
|
|
||||||
|
|
||||||
if (!rules["or"]) {
|
|
||||||
rules = {"or": [rules]}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var part of rules["or"]) {
|
|
||||||
if (!part["and"]) {
|
|
||||||
part = {"and": [part]}
|
|
||||||
}
|
|
||||||
var this_branch_without_product_filter = true;
|
|
||||||
for (var subpart of part["and"]) {
|
|
||||||
if (subpart["inList"]) {
|
|
||||||
if (subpart["inList"][0]["var"] === "product" && subpart["inList"][1]) {
|
|
||||||
this_branch_without_product_filter = false;
|
|
||||||
for (var listentry of subpart["inList"][1]["objectList"]) {
|
|
||||||
products_seen[parseInt(listentry["lookup"][1])] = true
|
|
||||||
}
|
|
||||||
} else if (subpart["inList"][0]["var"] === "variation" && subpart["inList"][1]) {
|
|
||||||
this_branch_without_product_filter = false;
|
|
||||||
for (var listentry_ of subpart["inList"][1]["objectList"]) {
|
|
||||||
variations_seen[parseInt(listentry_["lookup"][1])] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (this_branch_without_product_filter) {
|
|
||||||
branch_without_product_filter = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (branch_without_product_filter || (!Object.keys(products_seen).length && !Object.keys(variations_seen).length)) {
|
|
||||||
// At least one branch with no product filters at all – that's fine.
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
var missing = [];
|
|
||||||
for (var item of this.items) {
|
|
||||||
if (products_seen[item.id]) continue;
|
|
||||||
if (!this.all_products && !this.limit_products.includes(item.id)) continue;
|
|
||||||
if (item.variations.length > 0) {
|
|
||||||
for (var variation of item.variations) {
|
|
||||||
if (variations_seen[variation.id]) continue;
|
|
||||||
missing.push(item.name + " – " + variation.name)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
missing.push(item.name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return missing;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created: function () {
|
|
||||||
this.rules = JSON.parse($("#id_rules").val());
|
|
||||||
if ($("#items").length) {
|
|
||||||
this.items = JSON.parse($("#items").html());
|
|
||||||
|
|
||||||
var root = this.$root
|
|
||||||
|
|
||||||
function _update() {
|
|
||||||
root.all_products = $("#id_all_products").prop("checked")
|
|
||||||
root.limit_products = $("input[name=limit_products]:checked").map(function () {
|
|
||||||
return parseInt($(this).val())
|
|
||||||
}).toArray()
|
|
||||||
}
|
|
||||||
|
|
||||||
$("#id_all_products, input[name=limit_products]").on("change", function () {
|
|
||||||
_update();
|
|
||||||
})
|
|
||||||
_update()
|
|
||||||
|
|
||||||
function check_for_invalid_ids(valid_products, valid_variations, rule) {
|
|
||||||
if (rule["and"]) {
|
|
||||||
for(const child of rule["and"])
|
|
||||||
check_for_invalid_ids(valid_products, valid_variations, child);
|
|
||||||
} else if (rule["or"]) {
|
|
||||||
for(const child of rule["or"])
|
|
||||||
check_for_invalid_ids(valid_products, valid_variations, child);
|
|
||||||
} else if (rule["inList"] && rule["inList"][0]["var"] === "product") {
|
|
||||||
for(const item of rule["inList"][1]["objectList"]) {
|
|
||||||
if (!valid_products[item["lookup"][1]])
|
|
||||||
item["lookup"][2] = "[" + gettext('Error: Product not found!') + "]";
|
|
||||||
else
|
|
||||||
item["lookup"][2] = valid_products[item["lookup"][1]];
|
|
||||||
}
|
|
||||||
} else if (rule["inList"] && rule["inList"][0]["var"] === "variation") {
|
|
||||||
for(const item of rule["inList"][1]["objectList"]) {
|
|
||||||
if (!valid_variations[item["lookup"][1]])
|
|
||||||
item["lookup"][2] = "[" + gettext('Error: Variation not found!') + "]";
|
|
||||||
else
|
|
||||||
item["lookup"][2] = valid_variations[item["lookup"][1]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
check_for_invalid_ids(
|
|
||||||
Object.fromEntries(this.items.map(p => [p.id, p.name])),
|
|
||||||
Object.fromEntries(this.items.flatMap(p => p.variations?.map(v => [v.id, p.name + ' – ' + v.name]))),
|
|
||||||
this.rules
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
rules: {
|
|
||||||
deep: true,
|
|
||||||
handler: function (newval) {
|
|
||||||
$("#id_rules").val(JSON.stringify(newval));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
98
src/pretix/static/pretixcontrol/js/ui/checkinrules/App.vue
Normal file
98
src/pretix/static/pretixcontrol/js/ui/checkinrules/App.vue
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { rules as rawRules, items, allProducts, limitProducts } from './django-interop'
|
||||||
|
import { convertToDNF } from './jsonlogic-boolalg'
|
||||||
|
|
||||||
|
import RulesEditor from './checkin-rules-editor.vue'
|
||||||
|
import RulesVisualization from './checkin-rules-visualization.vue'
|
||||||
|
|
||||||
|
const gettext = (window as any).gettext
|
||||||
|
|
||||||
|
const missingItems = computed(() => {
|
||||||
|
// This computed variable contains list of item or variation names that
|
||||||
|
// a) Are allowed on the checkin list according to all_products or include_products
|
||||||
|
// b) Are not matched by ANY logical branch of the rule.
|
||||||
|
// The list will be empty if there is a "catch-all" rule.
|
||||||
|
let productsSeen = {}
|
||||||
|
let variationsSeen = {}
|
||||||
|
let rules = convertToDNF(rawRules.value)
|
||||||
|
let branchWithoutProductFilter = false
|
||||||
|
|
||||||
|
if (!rules.or) {
|
||||||
|
rules = { or: [rules] }
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let part of rules.or) {
|
||||||
|
if (!part.and) {
|
||||||
|
part = { and: [part] }
|
||||||
|
}
|
||||||
|
let thisBranchWithoutProductFilter = true
|
||||||
|
for (let subpart of part.and) {
|
||||||
|
if (subpart.inList) {
|
||||||
|
if (subpart.inList[0].var === 'product' && subpart.inList[1]) {
|
||||||
|
thisBranchWithoutProductFilter = false
|
||||||
|
for (let listentry of subpart.inList[1].objectList) {
|
||||||
|
productsSeen[parseInt(listentry.lookup[1])] = true
|
||||||
|
}
|
||||||
|
} else if (subpart.inList[0].var === 'variation' && subpart.inList[1]) {
|
||||||
|
thisBranchWithoutProductFilter = false
|
||||||
|
for (let listentry_ of subpart.inList[1].objectList) {
|
||||||
|
variationsSeen[parseInt(listentry_.lookup[1])] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (thisBranchWithoutProductFilter) {
|
||||||
|
branchWithoutProductFilter = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (branchWithoutProductFilter || (!Object.keys(productsSeen).length && !Object.keys(variationsSeen).length)) {
|
||||||
|
// At least one branch with no product filters at all – that's fine.
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
let missing = []
|
||||||
|
for (const item of items.value) {
|
||||||
|
if (productsSeen[item.id]) continue
|
||||||
|
if (!allProducts.value && !limitProducts.value.includes(item.id)) continue
|
||||||
|
if (item.variations.length > 0) {
|
||||||
|
for (let variation of item.variations) {
|
||||||
|
if (variationsSeen[variation.id]) continue
|
||||||
|
missing.push(item.name + ' – ' + variation.name)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
missing.push(item.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return missing
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<template lang="pug">
|
||||||
|
#rules-editor.form-inline
|
||||||
|
div
|
||||||
|
ul.nav.nav-tabs(role="tablist")
|
||||||
|
li.active(role="presentation")
|
||||||
|
a(href="#rules-edit", role="tab", data-toggle="tab")
|
||||||
|
span.fa.fa-edit
|
||||||
|
| {{ gettext("Edit") }}
|
||||||
|
li(role="presentation")
|
||||||
|
a(href="#rules-viz", role="tab", data-toggle="tab")
|
||||||
|
span.fa.fa-eye
|
||||||
|
| {{ gettext("Visualize") }}
|
||||||
|
|
||||||
|
//- Tab panes
|
||||||
|
.tab-content
|
||||||
|
#rules-edit.tab-pane.active(v-if="items", role="tabpanel")
|
||||||
|
RulesEditor
|
||||||
|
#rules-viz.tab-pane(role="tabpanel")
|
||||||
|
RulesVisualization
|
||||||
|
|
||||||
|
.alert.alert-info(v-if="missingItems.length")
|
||||||
|
p {{ gettext("Your rule always filters by product or variation, but the following products or variations are not contained in any of your rule parts so people with these tickets will not get in:") }}
|
||||||
|
ul
|
||||||
|
li(v-for="h in missingItems", :key="h") {{ h }}
|
||||||
|
p {{ gettext("Please double-check if this was intentional.") }}
|
||||||
|
</template>
|
||||||
|
<style lang="stylus">
|
||||||
|
</style>
|
||||||
@@ -1,355 +1,365 @@
|
|||||||
<template>
|
<script setup lang="ts">
|
||||||
<div v-bind:class="classObject">
|
/* eslint-disable vue/no-mutating-props */
|
||||||
<div class="btn-group pull-right">
|
import { computed } from 'vue'
|
||||||
<button type="button" class="checkin-rule-remove btn btn-xs btn-default" @click.prevent="duplicate"
|
import { TEXTS, VARS, TYPEOPS } from './constants'
|
||||||
v-if="level > 0" data-toggle="tooltip" :title="texts.duplicate">
|
import { productSelectURL, variationSelectURL, gateSelectURL } from './django-interop'
|
||||||
<span class="fa fa-copy"></span>
|
import LookupSelect2 from './lookup-select2.vue'
|
||||||
</button>
|
import Datetimefield from './datetimefield.vue'
|
||||||
<button type="button" class="checkin-rule-remove btn btn-xs btn-default" @click.prevent="wrapWithOR">OR
|
import Timefield from './timefield.vue'
|
||||||
</button>
|
|
||||||
<button type="button" class="checkin-rule-remove btn btn-xs btn-default" @click.prevent="wrapWithAND">AND
|
|
||||||
</button>
|
|
||||||
<button type="button" class="checkin-rule-remove btn btn-xs btn-default" @click.prevent="cutOut"
|
|
||||||
v-if="operands && operands.length === 1 && (operator === 'or' || operator === 'and')"><span
|
|
||||||
class="fa fa-cut"></span></button>
|
|
||||||
<button type="button" class="checkin-rule-remove btn btn-xs btn-default" @click.prevent="remove"
|
|
||||||
v-if="level > 0"><span class="fa fa-trash"></span></button>
|
|
||||||
</div>
|
|
||||||
<select v-bind:value="variable" v-on:input="setVariable" required class="form-control">
|
|
||||||
<option value="and">{{texts.and}}</option>
|
|
||||||
<option value="or">{{texts.or}}</option>
|
|
||||||
<option v-for="(v, name) in vars" :value="name">{{ v.label }}</option>
|
|
||||||
</select>
|
|
||||||
<select v-bind:value="operator" v-on:input="setOperator" required class="form-control"
|
|
||||||
v-if="operator !== 'or' && operator !== 'and' && vartype !== 'int_by_datetime'">
|
|
||||||
<option></option>
|
|
||||||
<option v-for="(v, name) in operators" :value="name">{{ v.label }}</option>
|
|
||||||
</select>
|
|
||||||
<select v-bind:value="timeType" v-on:input="setTimeType" required class="form-control"
|
|
||||||
v-if="vartype === 'datetime' || vartype === 'int_by_datetime'">
|
|
||||||
<option value="date_from">{{texts.date_from}}</option>
|
|
||||||
<option value="date_to">{{texts.date_to}}</option>
|
|
||||||
<option value="date_admission">{{texts.date_admission}}</option>
|
|
||||||
<option value="custom">{{texts.date_custom}}</option>
|
|
||||||
<option value="customtime">{{texts.date_customtime}}</option>
|
|
||||||
</select>
|
|
||||||
<datetimefield v-if="(vartype === 'datetime' || vartype === 'int_by_datetime') && timeType === 'custom'" :value="timeValue"
|
|
||||||
v-on:input="setTimeValue"></datetimefield>
|
|
||||||
<timefield v-if="(vartype === 'datetime' || vartype === 'int_by_datetime') && timeType === 'customtime'" :value="timeValue"
|
|
||||||
v-on:input="setTimeValue"></timefield>
|
|
||||||
<input class="form-control" required type="number"
|
|
||||||
v-if="vartype === 'datetime' && timeType && timeType !== 'customtime' && timeType !== 'custom'" v-bind:value="timeTolerance"
|
|
||||||
v-on:input="setTimeTolerance" :placeholder="texts.date_tolerance">
|
|
||||||
<select v-bind:value="operator" v-on:input="setOperator" required class="form-control"
|
|
||||||
v-if="vartype === 'int_by_datetime'">
|
|
||||||
<option></option>
|
|
||||||
<option v-for="(v, name) in operators" :value="name">{{ v.label }}</option>
|
|
||||||
</select>
|
|
||||||
<input class="form-control" required type="number" v-if="(vartype === 'int' || vartype === 'int_by_datetime') && cardinality > 1"
|
|
||||||
v-bind:value="rightoperand" v-on:input="setRightOperandNumber">
|
|
||||||
<lookup-select2 required v-if="vartype === 'product' && operator === 'inList'" :multiple="true"
|
|
||||||
:value="rightoperand" v-on:input="setRightOperandProductList"
|
|
||||||
:url="productSelectURL"></lookup-select2>
|
|
||||||
<lookup-select2 required v-if="vartype === 'variation' && operator === 'inList'" :multiple="true"
|
|
||||||
:value="rightoperand" v-on:input="setRightOperandVariationList"
|
|
||||||
:url="variationSelectURL"></lookup-select2>
|
|
||||||
<lookup-select2 required v-if="vartype === 'gate' && operator === 'inList'" :multiple="true"
|
|
||||||
:value="rightoperand" v-on:input="setRightOperandGateList"
|
|
||||||
:url="gateSelectURL"></lookup-select2>
|
|
||||||
<select required v-if="vartype === 'enum_entry_status' && operator === '=='"
|
|
||||||
:value="rightoperand" v-on:input="setRightOperandEnum" class="form-control">
|
|
||||||
<option value="absent">{{ texts.status_absent }}</option>
|
|
||||||
<option value="present">{{ texts.status_present }}</option>
|
|
||||||
</select>
|
|
||||||
<div class="checkin-rule-childrules" v-if="operator === 'or' || operator === 'and'">
|
|
||||||
<div v-for="(op, opi) in operands">
|
|
||||||
<checkin-rule :rule="op" :index="opi" :level="level + 1" v-if="typeof op === 'object'"></checkin-rule>
|
|
||||||
</div>
|
|
||||||
<button type="button" class="checkin-rule-addchild btn btn-xs btn-default" @click.prevent="addOperand"><span
|
|
||||||
class="fa fa-plus-circle"></span> {{ texts.condition_add }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
LookupSelect2: LookupSelect2.default,
|
|
||||||
Datetimefield: Datetimefield.default,
|
|
||||||
Timefield: Timefield.default,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
rule: Object,
|
|
||||||
level: Number,
|
|
||||||
index: Number,
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
texts: function () {
|
|
||||||
return this.$root.texts;
|
|
||||||
},
|
|
||||||
variable: function () {
|
|
||||||
var op = this.operator;
|
|
||||||
if (op === "and" || op === "or") {
|
|
||||||
return op;
|
|
||||||
} else if (this.rule[op] && this.rule[op][0]) {
|
|
||||||
if (this.rule[op][0]["entries_since"]) {
|
|
||||||
return "entries_since";
|
|
||||||
}
|
|
||||||
if (this.rule[op][0]["entries_before"]) {
|
|
||||||
return "entries_before";
|
|
||||||
}
|
|
||||||
if (this.rule[op][0]["entries_days_since"]) {
|
|
||||||
return "entries_days_since";
|
|
||||||
}
|
|
||||||
if (this.rule[op][0]["entries_days_before"]) {
|
|
||||||
return "entries_days_before";
|
|
||||||
}
|
|
||||||
return this.rule[op][0]["var"];
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
rightoperand: function () {
|
|
||||||
var op = this.operator;
|
|
||||||
if (op === "and" || op === "or") {
|
|
||||||
return null;
|
|
||||||
} else if (this.rule[op] && typeof this.rule[op][1] !== "undefined") {
|
|
||||||
return this.rule[op][1];
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
operator: function () {
|
|
||||||
return Object.keys(this.rule)[0];
|
|
||||||
},
|
|
||||||
operands: function () {
|
|
||||||
return this.rule[this.operator];
|
|
||||||
},
|
|
||||||
classObject: function () {
|
|
||||||
var c = {
|
|
||||||
'checkin-rule': true
|
|
||||||
};
|
|
||||||
c['checkin-rule-' + this.variable] = true;
|
|
||||||
return c;
|
|
||||||
},
|
|
||||||
vartype: function () {
|
|
||||||
if (this.variable && this.$root.VARS[this.variable]) {
|
|
||||||
return this.$root.VARS[this.variable]['type'];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
timeType: function () {
|
|
||||||
if (this.vartype === 'int_by_datetime') {
|
|
||||||
if (this.rule[this.operator][0][this.variable] && this.rule[this.operator][0][this.variable][0]['buildTime']) {
|
|
||||||
return this.rule[this.operator][0][this.variable][0]['buildTime'][0];
|
|
||||||
}
|
|
||||||
} else if (this.rightoperand && this.rightoperand['buildTime']) {
|
|
||||||
return this.rightoperand['buildTime'][0];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
timeTolerance: function () {
|
|
||||||
var op = this.operator;
|
|
||||||
if ((op === "isBefore" || op === "isAfter") && this.rule[op] && typeof this.rule[op][2] !== "undefined") {
|
|
||||||
return this.rule[op][2];
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
timeValue: function () {
|
|
||||||
if (this.vartype === 'int_by_datetime') {
|
|
||||||
if (this.rule[this.operator][0][this.variable][0]['buildTime']) {
|
|
||||||
return this.rule[this.operator][0][this.variable][0]['buildTime'][1];
|
|
||||||
}
|
|
||||||
} else if (this.rightoperand && this.rightoperand['buildTime']) {
|
|
||||||
return this.rightoperand['buildTime'][1];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
cardinality: function () {
|
|
||||||
if (this.vartype && this.$root.TYPEOPS[this.vartype] && this.$root.TYPEOPS[this.vartype][this.operator]) {
|
|
||||||
return this.$root.TYPEOPS[this.vartype][this.operator]['cardinality'];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
operators: function () {
|
|
||||||
return this.$root.TYPEOPS[this.vartype];
|
|
||||||
},
|
|
||||||
productSelectURL: function () {
|
|
||||||
return $("#product-select2").text();
|
|
||||||
},
|
|
||||||
variationSelectURL: function () {
|
|
||||||
return $("#variations-select2").text();
|
|
||||||
},
|
|
||||||
gateSelectURL: function () {
|
|
||||||
return $("#gates-select2").text();
|
|
||||||
},
|
|
||||||
vars: function () {
|
|
||||||
return this.$root.VARS;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
setVariable: function (event) {
|
|
||||||
var current_op = Object.keys(this.rule)[0];
|
|
||||||
var current_val = this.rule[current_op];
|
|
||||||
|
|
||||||
if (event.target.value === "and" || event.target.value === "or") {
|
const props = defineProps<{
|
||||||
if (current_val[0] && current_val[0]["var"]) {
|
rule: any
|
||||||
current_val = [];
|
level: number
|
||||||
|
index: number
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
remove: []
|
||||||
|
duplicate: []
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const operator = computed(() => Object.keys(props.rule)[0])
|
||||||
|
const operands = computed(() => props.rule[operator.value])
|
||||||
|
|
||||||
|
const variable = computed(() => {
|
||||||
|
const op = operator.value
|
||||||
|
if (op === 'and' || op === 'or') {
|
||||||
|
return op
|
||||||
|
} else if (props.rule[op]?.[0]) {
|
||||||
|
if (props.rule[op][0]['entries_since']) return 'entries_since'
|
||||||
|
if (props.rule[op][0]['entries_before']) return 'entries_before'
|
||||||
|
if (props.rule[op][0]['entries_days_since']) return 'entries_days_since'
|
||||||
|
if (props.rule[op][0]['entries_days_before']) return 'entries_days_before'
|
||||||
|
return props.rule[op][0]['var']
|
||||||
}
|
}
|
||||||
this.$set(this.rule, event.target.value, current_val);
|
return null
|
||||||
this.$delete(this.rule, current_op);
|
})
|
||||||
|
|
||||||
|
const rightoperand = computed(() => {
|
||||||
|
const op = operator.value
|
||||||
|
if (op === 'and' || op === 'or') return null
|
||||||
|
return props.rule[op]?.[1] ?? null
|
||||||
|
})
|
||||||
|
|
||||||
|
const classObject = computed(() => ({
|
||||||
|
'checkin-rule': true,
|
||||||
|
['checkin-rule-' + variable.value]: true
|
||||||
|
}))
|
||||||
|
|
||||||
|
const vartype = computed(() => VARS[variable.value]?.type)
|
||||||
|
|
||||||
|
const timeType = computed(() => {
|
||||||
|
if (vartype.value === 'int_by_datetime') {
|
||||||
|
return props.rule[operator.value]?.[0]?.[variable.value]?.[0]?.buildTime?.[0]
|
||||||
|
}
|
||||||
|
return rightoperand.value?.buildTime?.[0]
|
||||||
|
})
|
||||||
|
|
||||||
|
const timeTolerance = computed(() => {
|
||||||
|
const op = operator.value
|
||||||
|
if ((op === 'isBefore' || op === 'isAfter') && props.rule[op]?.[2] !== undefined) {
|
||||||
|
return props.rule[op][2]
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
|
||||||
|
const timeValue = computed(() => {
|
||||||
|
if (vartype.value === 'int_by_datetime') {
|
||||||
|
return props.rule[operator.value]?.[0]?.[variable.value]?.[0]?.buildTime?.[1]
|
||||||
|
}
|
||||||
|
return rightoperand.value?.buildTime?.[1]
|
||||||
|
})
|
||||||
|
|
||||||
|
const cardinality = computed(() => TYPEOPS[vartype.value]?.[operator.value]?.cardinality)
|
||||||
|
const operators = computed(() => TYPEOPS[vartype.value])
|
||||||
|
|
||||||
|
function setVariable (event: Event) {
|
||||||
|
const target = event.target as HTMLSelectElement
|
||||||
|
const currentOp = Object.keys(props.rule)[0]
|
||||||
|
let currentVal = props.rule[currentOp]
|
||||||
|
|
||||||
|
if (target.value === 'and' || target.value === 'or') {
|
||||||
|
if (currentVal[0]?.var) currentVal = []
|
||||||
|
props.rule[target.value] = currentVal
|
||||||
|
delete props.rule[currentOp]
|
||||||
} else {
|
} else {
|
||||||
if (current_val !== "and" && current_val !== "or" && current_val[0] && this.$root.VARS[event.target.value]['type'] === this.vartype) {
|
if (currentVal !== 'and' && currentVal !== 'or' && currentVal[0] && VARS[target.value]?.type === vartype.value) {
|
||||||
if (this.vartype === "int_by_datetime") {
|
if (vartype.value === 'int_by_datetime') {
|
||||||
var current_data = this.rule[current_op][0][this.variable];
|
const currentData = props.rule[currentOp][0][variable.value]
|
||||||
var new_lhs = {};
|
props.rule[currentOp][0] = { [target.value]: JSON.parse(JSON.stringify(currentData)) }
|
||||||
new_lhs[event.target.value] = JSON.parse(JSON.stringify(current_data));
|
|
||||||
this.$set(this.rule[current_op], 0, new_lhs);
|
|
||||||
} else {
|
} else {
|
||||||
this.$set(this.rule[current_op][0], "var", event.target.value);
|
props.rule[currentOp][0].var = target.value
|
||||||
}
|
}
|
||||||
} else if (this.$root.VARS[event.target.value]['type'] === 'int_by_datetime') {
|
} else if (VARS[target.value]?.type === 'int_by_datetime') {
|
||||||
this.$delete(this.rule, current_op);
|
delete props.rule[currentOp]
|
||||||
var o = {};
|
props.rule['!!'] = [{ [target.value]: [{ buildTime: [null, null] }] }]
|
||||||
o[event.target.value] = [{"buildTime": [null, null]}]
|
|
||||||
this.$set(this.rule, "!!", [o]);
|
|
||||||
} else {
|
} else {
|
||||||
this.$delete(this.rule, current_op);
|
delete props.rule[currentOp]
|
||||||
this.$set(this.rule, "!!", [{"var": event.target.value}]);
|
props.rule['!!'] = [{ var: target.value }]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
setOperator: function (event) {
|
|
||||||
var current_op = Object.keys(this.rule)[0];
|
function setOperator (event: Event) {
|
||||||
var current_val = this.rule[current_op];
|
const target = event.target as HTMLSelectElement
|
||||||
this.$delete(this.rule, current_op);
|
const currentOp = Object.keys(props.rule)[0]
|
||||||
this.$set(this.rule, event.target.value, current_val);
|
const currentVal = props.rule[currentOp]
|
||||||
},
|
delete props.rule[currentOp]
|
||||||
setRightOperandNumber: function (event) {
|
props.rule[target.value] = currentVal
|
||||||
if (this.rule[this.operator].length === 1) {
|
}
|
||||||
this.rule[this.operator].push(parseInt(event.target.value));
|
|
||||||
|
function setRightOperandNumber (event: Event) {
|
||||||
|
const val = parseInt((event.target as HTMLInputElement).value)
|
||||||
|
if (props.rule[operator.value].length === 1) {
|
||||||
|
props.rule[operator.value].push(val)
|
||||||
} else {
|
} else {
|
||||||
this.$set(this.rule[this.operator], 1, parseInt(event.target.value));
|
props.rule[operator.value][1] = val
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
setTimeTolerance: function (event) {
|
|
||||||
if (this.rule[this.operator].length === 2) {
|
function setTimeTolerance (event: Event) {
|
||||||
this.rule[this.operator].push(parseInt(event.target.value));
|
const val = parseInt((event.target as HTMLInputElement).value)
|
||||||
|
if (props.rule[operator.value].length === 2) {
|
||||||
|
props.rule[operator.value].push(val)
|
||||||
} else {
|
} else {
|
||||||
this.$set(this.rule[this.operator], 2, parseInt(event.target.value));
|
props.rule[operator.value][2] = val
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
setTimeType: function (event) {
|
|
||||||
var time = {
|
function setTimeType (event: Event) {
|
||||||
"buildTime": [event.target.value]
|
const val = (event.target as HTMLSelectElement).value
|
||||||
};
|
const time = { buildTime: [val] }
|
||||||
if (this.vartype === "int_by_datetime") {
|
if (vartype.value === 'int_by_datetime') {
|
||||||
this.$set(this.rule[this.operator][0][this.variable], 0, time);
|
props.rule[operator.value][0][variable.value][0] = time
|
||||||
} else {
|
} else {
|
||||||
if (this.rule[this.operator].length === 1) {
|
if (props.rule[operator.value].length === 1) {
|
||||||
this.rule[this.operator].push(time);
|
props.rule[operator.value].push(time)
|
||||||
} else {
|
} else {
|
||||||
this.$set(this.rule[this.operator], 1, time);
|
props.rule[operator.value][1] = time
|
||||||
}
|
}
|
||||||
if (event.target.value === "custom") {
|
if (val === 'custom') {
|
||||||
this.$set(this.rule[this.operator], 2, 0);
|
props.rule[operator.value][2] = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
setTimeValue: function (val) {
|
|
||||||
if (this.vartype === "int_by_datetime") {
|
function setTimeValue (val: string) {
|
||||||
this.$set(this.rule[this.operator][0][this.variable][0]["buildTime"], 1, val);
|
if (vartype.value === 'int_by_datetime') {
|
||||||
|
props.rule[operator.value][0][variable.value][0]['buildTime'][1] = val
|
||||||
} else {
|
} else {
|
||||||
this.$set(this.rule[this.operator][1]["buildTime"], 1, val);
|
props.rule[operator.value][1]['buildTime'][1] = val
|
||||||
}
|
}
|
||||||
},
|
|
||||||
setRightOperandProductList: function (val) {
|
|
||||||
var products = {
|
|
||||||
"objectList": []
|
|
||||||
};
|
|
||||||
for (var i = 0; i < val.length; i++) {
|
|
||||||
products["objectList"].push({
|
|
||||||
"lookup": [
|
|
||||||
"product",
|
|
||||||
val[i].id,
|
|
||||||
val[i].text
|
|
||||||
]
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
if (this.rule[this.operator].length === 1) {
|
|
||||||
this.rule[this.operator].push(products);
|
function setRightOperandProductList (val: { id: any; text: string }[]) {
|
||||||
|
const products = { objectList: val.map(item => ({ lookup: ['product', item.id, item.text] })) }
|
||||||
|
if (props.rule[operator.value].length === 1) {
|
||||||
|
props.rule[operator.value].push(products)
|
||||||
} else {
|
} else {
|
||||||
this.$set(this.rule[this.operator], 1, products);
|
props.rule[operator.value][1] = products
|
||||||
}
|
}
|
||||||
},
|
|
||||||
setRightOperandVariationList: function (val) {
|
|
||||||
var products = {
|
|
||||||
"objectList": []
|
|
||||||
};
|
|
||||||
for (var i = 0; i < val.length; i++) {
|
|
||||||
products["objectList"].push({
|
|
||||||
"lookup": [
|
|
||||||
"variation",
|
|
||||||
val[i].id,
|
|
||||||
val[i].text
|
|
||||||
]
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
if (this.rule[this.operator].length === 1) {
|
|
||||||
this.rule[this.operator].push(products);
|
function setRightOperandVariationList (val: { id: any; text: string }[]) {
|
||||||
|
const products = { objectList: val.map(item => ({ lookup: ['variation', item.id, item.text] })) }
|
||||||
|
if (props.rule[operator.value].length === 1) {
|
||||||
|
props.rule[operator.value].push(products)
|
||||||
} else {
|
} else {
|
||||||
this.$set(this.rule[this.operator], 1, products);
|
props.rule[operator.value][1] = products
|
||||||
}
|
}
|
||||||
},
|
|
||||||
setRightOperandGateList: function (val) {
|
|
||||||
var products = {
|
|
||||||
"objectList": []
|
|
||||||
};
|
|
||||||
for (var i = 0; i < val.length; i++) {
|
|
||||||
products["objectList"].push({
|
|
||||||
"lookup": [
|
|
||||||
"gate",
|
|
||||||
val[i].id,
|
|
||||||
val[i].text
|
|
||||||
]
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
if (this.rule[this.operator].length === 1) {
|
|
||||||
this.rule[this.operator].push(products);
|
function setRightOperandGateList (val: { id: any; text: string }[]) {
|
||||||
|
const products = { objectList: val.map(item => ({ lookup: ['gate', item.id, item.text] })) }
|
||||||
|
if (props.rule[operator.value].length === 1) {
|
||||||
|
props.rule[operator.value].push(products)
|
||||||
} else {
|
} else {
|
||||||
this.$set(this.rule[this.operator], 1, products);
|
props.rule[operator.value][1] = products
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
setRightOperandEnum: function (event) {
|
|
||||||
if (this.rule[this.operator].length === 1) {
|
function setRightOperandEnum (event: Event) {
|
||||||
this.rule[this.operator].push(event.target.value);
|
const val = (event.target as HTMLSelectElement).value
|
||||||
|
if (props.rule[operator.value].length === 1) {
|
||||||
|
props.rule[operator.value].push(val)
|
||||||
} else {
|
} else {
|
||||||
this.$set(this.rule[this.operator], 1, event.target.value);
|
props.rule[operator.value][1] = val
|
||||||
}
|
}
|
||||||
},
|
|
||||||
addOperand: function () {
|
|
||||||
this.rule[this.operator].push({"": []});
|
|
||||||
},
|
|
||||||
wrapWithOR: function () {
|
|
||||||
var r = JSON.parse(JSON.stringify(this.rule));
|
|
||||||
this.$delete(this.rule, this.operator);
|
|
||||||
this.$set(this.rule, "or", [r]);
|
|
||||||
},
|
|
||||||
wrapWithAND: function () {
|
|
||||||
var r = JSON.parse(JSON.stringify(this.rule));
|
|
||||||
this.$delete(this.rule, this.operator);
|
|
||||||
this.$set(this.rule, "and", [r]);
|
|
||||||
},
|
|
||||||
cutOut: function () {
|
|
||||||
var cop = Object.keys(this.operands[0])[0];
|
|
||||||
var r = this.operands[0][cop];
|
|
||||||
this.$delete(this.rule, this.operator);
|
|
||||||
this.$set(this.rule, cop, r);
|
|
||||||
},
|
|
||||||
remove: function () {
|
|
||||||
this.$parent.rule[this.$parent.operator].splice(this.index, 1);
|
|
||||||
},
|
|
||||||
duplicate: function () {
|
|
||||||
var r = JSON.parse(JSON.stringify(this.rule));
|
|
||||||
this.$parent.rule[this.$parent.operator].splice(this.index, 0, r);
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addOperand () {
|
||||||
|
props.rule[operator.value].push({ '': [] })
|
||||||
|
}
|
||||||
|
|
||||||
|
function wrapWithOR () {
|
||||||
|
const r = JSON.parse(JSON.stringify(props.rule))
|
||||||
|
delete props.rule[operator.value]
|
||||||
|
props.rule.or = [r]
|
||||||
|
}
|
||||||
|
|
||||||
|
function wrapWithAND () {
|
||||||
|
const r = JSON.parse(JSON.stringify(props.rule))
|
||||||
|
delete props.rule[operator.value]
|
||||||
|
props.rule.and = [r]
|
||||||
|
}
|
||||||
|
|
||||||
|
function cutOut () {
|
||||||
|
const cop = Object.keys(operands.value[0])[0]
|
||||||
|
const r = operands.value[0][cop]
|
||||||
|
delete props.rule[operator.value]
|
||||||
|
props.rule[cop] = r
|
||||||
|
}
|
||||||
|
|
||||||
|
function remove () {
|
||||||
|
emit('remove')
|
||||||
|
}
|
||||||
|
|
||||||
|
function duplicate () {
|
||||||
|
emit('duplicate')
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeChild (index: number) {
|
||||||
|
props.rule[operator.value].splice(index, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
function duplicateChild (index: number) {
|
||||||
|
const r = JSON.parse(JSON.stringify(props.rule[operator.value][index]))
|
||||||
|
props.rule[operator.value].splice(index, 0, r)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
<template lang="pug">
|
||||||
|
div(:class="classObject")
|
||||||
|
.btn-group.pull-right
|
||||||
|
button.checkin-rule-remove.btn.btn-xs.btn-default(
|
||||||
|
v-if="level > 0",
|
||||||
|
type="button",
|
||||||
|
data-toggle="tooltip",
|
||||||
|
:title="TEXTS.duplicate",
|
||||||
|
@click.prevent="duplicate"
|
||||||
|
)
|
||||||
|
span.fa.fa-copy
|
||||||
|
button.checkin-rule-remove.btn.btn-xs.btn-default(
|
||||||
|
type="button",
|
||||||
|
@click.prevent="wrapWithOR"
|
||||||
|
) OR
|
||||||
|
button.checkin-rule-remove.btn.btn-xs.btn-default(
|
||||||
|
type="button",
|
||||||
|
@click.prevent="wrapWithAND"
|
||||||
|
) AND
|
||||||
|
button.checkin-rule-remove.btn.btn-xs.btn-default(
|
||||||
|
v-if="operands && operands.length === 1 && (operator === 'or' || operator === 'and')",
|
||||||
|
type="button",
|
||||||
|
@click.prevent="cutOut"
|
||||||
|
)
|
||||||
|
span.fa.fa-cut
|
||||||
|
button.checkin-rule-remove.btn.btn-xs.btn-default(
|
||||||
|
v-if="level > 0",
|
||||||
|
type="button",
|
||||||
|
@click.prevent="remove"
|
||||||
|
)
|
||||||
|
span.fa.fa-trash
|
||||||
|
select.form-control(:value="variable", required, @input="setVariable")
|
||||||
|
option(value="and") {{ TEXTS.and }}
|
||||||
|
option(value="or") {{ TEXTS.or }}
|
||||||
|
option(v-for="(v, name) in VARS", :key="name", :value="name") {{ v.label }}
|
||||||
|
select.form-control(
|
||||||
|
v-if="operator !== 'or' && operator !== 'and' && vartype !== 'int_by_datetime'",
|
||||||
|
:value="operator",
|
||||||
|
required,
|
||||||
|
@input="setOperator"
|
||||||
|
)
|
||||||
|
option
|
||||||
|
option(v-for="(v, name) in operators", :key="name", :value="name") {{ v.label }}
|
||||||
|
select.form-control(
|
||||||
|
v-if="vartype === 'datetime' || vartype === 'int_by_datetime'",
|
||||||
|
:value="timeType",
|
||||||
|
required,
|
||||||
|
@input="setTimeType"
|
||||||
|
)
|
||||||
|
option(value="date_from") {{ TEXTS.date_from }}
|
||||||
|
option(value="date_to") {{ TEXTS.date_to }}
|
||||||
|
option(value="date_admission") {{ TEXTS.date_admission }}
|
||||||
|
option(value="custom") {{ TEXTS.date_custom }}
|
||||||
|
option(value="customtime") {{ TEXTS.date_customtime }}
|
||||||
|
Datetimefield(
|
||||||
|
v-if="(vartype === 'datetime' || vartype === 'int_by_datetime') && timeType === 'custom'",
|
||||||
|
:value="timeValue",
|
||||||
|
@input="setTimeValue"
|
||||||
|
)
|
||||||
|
Timefield(
|
||||||
|
v-if="(vartype === 'datetime' || vartype === 'int_by_datetime') && timeType === 'customtime'",
|
||||||
|
:value="timeValue",
|
||||||
|
@input="setTimeValue"
|
||||||
|
)
|
||||||
|
input.form-control(
|
||||||
|
v-if="vartype === 'datetime' && timeType && timeType !== 'customtime' && timeType !== 'custom'",
|
||||||
|
required,
|
||||||
|
type="number",
|
||||||
|
:value="timeTolerance",
|
||||||
|
:placeholder="TEXTS.date_tolerance",
|
||||||
|
@input="setTimeTolerance"
|
||||||
|
)
|
||||||
|
select.form-control(
|
||||||
|
v-if="vartype === 'int_by_datetime'",
|
||||||
|
:value="operator",
|
||||||
|
required,
|
||||||
|
@input="setOperator"
|
||||||
|
)
|
||||||
|
option
|
||||||
|
option(v-for="(v, name) in operators", :key="name", :value="name") {{ v.label }}
|
||||||
|
input.form-control(
|
||||||
|
v-if="(vartype === 'int' || vartype === 'int_by_datetime') && cardinality > 1",
|
||||||
|
required,
|
||||||
|
type="number",
|
||||||
|
:value="rightoperand",
|
||||||
|
@input="setRightOperandNumber"
|
||||||
|
)
|
||||||
|
LookupSelect2(
|
||||||
|
v-if="vartype === 'product' && operator === 'inList'",
|
||||||
|
required,
|
||||||
|
:multiple="true",
|
||||||
|
:value="rightoperand",
|
||||||
|
:url="productSelectURL",
|
||||||
|
@input="setRightOperandProductList"
|
||||||
|
)
|
||||||
|
LookupSelect2(
|
||||||
|
v-if="vartype === 'variation' && operator === 'inList'",
|
||||||
|
required,
|
||||||
|
:multiple="true",
|
||||||
|
:value="rightoperand",
|
||||||
|
:url="variationSelectURL",
|
||||||
|
@input="setRightOperandVariationList"
|
||||||
|
)
|
||||||
|
LookupSelect2(
|
||||||
|
v-if="vartype === 'gate' && operator === 'inList'",
|
||||||
|
required,
|
||||||
|
:multiple="true",
|
||||||
|
:value="rightoperand",
|
||||||
|
:url="gateSelectURL",
|
||||||
|
@input="setRightOperandGateList"
|
||||||
|
)
|
||||||
|
select.form-control(
|
||||||
|
v-if="vartype === 'enum_entry_status' && operator === '=='",
|
||||||
|
required,
|
||||||
|
:value="rightoperand",
|
||||||
|
@input="setRightOperandEnum"
|
||||||
|
)
|
||||||
|
option(value="absent") {{ TEXTS.status_absent }}
|
||||||
|
option(value="present") {{ TEXTS.status_present }}
|
||||||
|
.checkin-rule-childrules(v-if="operator === 'or' || operator === 'and'")
|
||||||
|
div(v-for="(op, opi) in operands", :key="opi")
|
||||||
|
CheckinRule(
|
||||||
|
v-if="typeof op === 'object'",
|
||||||
|
:rule="op",
|
||||||
|
:index="opi",
|
||||||
|
:level="level + 1",
|
||||||
|
@remove="removeChild(opi)",
|
||||||
|
@duplicate="duplicateChild(opi)"
|
||||||
|
)
|
||||||
|
button.checkin-rule-addchild.btn.btn-xs.btn-default(
|
||||||
|
type="button",
|
||||||
|
@click.prevent="addOperand"
|
||||||
|
)
|
||||||
|
span.fa.fa-plus-circle
|
||||||
|
| {{ TEXTS.condition_add }}
|
||||||
|
</template>
|
||||||
|
|||||||
@@ -1,25 +1,23 @@
|
|||||||
<template>
|
<script setup lang="ts">
|
||||||
<div class="checkin-rules-editor">
|
import { computed } from 'vue'
|
||||||
<checkin-rule :rule="this.$root.rules" :level="0" :index="0" v-if="hasRules"></checkin-rule>
|
import { TEXTS } from './constants'
|
||||||
<button type="button" class="checkin-rule-addchild btn btn-xs btn-default" v-if="!hasRules"
|
import { rules } from './django-interop'
|
||||||
@click.prevent="addRule"><span class="fa fa-plus-circle"></span> {{ this.$root.texts.condition_add }}
|
import CheckinRule from './checkin-rule.vue'
|
||||||
</button>
|
|
||||||
</div>
|
const hasRules = computed(() => !!Object.keys(rules.value).length)
|
||||||
</template>
|
|
||||||
<script>
|
function addRule () {
|
||||||
export default {
|
rules.value.and = []
|
||||||
components: {
|
|
||||||
CheckinRule: CheckinRule.default,
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
hasRules: function () {
|
|
||||||
return !!Object.keys(this.$root.rules).length;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
addRule: function () {
|
|
||||||
this.$set(this.$root.rules, "and", []);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
<template lang="pug">
|
||||||
|
.checkin-rules-editor
|
||||||
|
CheckinRule(v-if="hasRules", :rule="rules", :level="0", :index="0")
|
||||||
|
button.checkin-rule-addchild.btn.btn-xs.btn-default(
|
||||||
|
v-if="!hasRules",
|
||||||
|
type="button",
|
||||||
|
@click.prevent="addRule"
|
||||||
|
)
|
||||||
|
span.fa.fa-plus-circle
|
||||||
|
| {{ TEXTS.condition_add }}
|
||||||
|
</template>
|
||||||
|
|||||||
@@ -1,51 +1,39 @@
|
|||||||
<template>
|
<script setup lang="ts">
|
||||||
<div :class="'checkin-rules-visualization ' + (maximized ? 'maximized' : '')">
|
import { ref, computed, watch, onMounted, onUnmounted, nextTick } from 'vue'
|
||||||
<div class="tools">
|
import { rules } from './django-interop'
|
||||||
<button v-if="maximized" class="btn btn-default" type="button" @click.prevent="maximized = false"><span class="fa fa-window-close"></span></button>
|
import VizNode from './viz-node.vue'
|
||||||
<button v-if="!maximized" class="btn btn-default" type="button" @click.prevent="maximized = true"><span class="fa fa-window-maximize"></span></button>
|
|
||||||
</div>
|
declare const d3: any
|
||||||
<svg :width="graph.columns * (boxWidth + marginX) + 2 * paddingX" :height="graph.height * (boxHeight + marginY)"
|
|
||||||
:viewBox="viewBox" ref="svg">
|
const svg = ref<SVGSVGElement | null>(null)
|
||||||
<g :transform="zoomTransform.toString()">
|
const maximized = ref(false)
|
||||||
<viz-node v-for="(node, nodeid) in graph.nodes_by_id" :key="nodeid" :node="node"
|
const zoom = ref<any>(null)
|
||||||
:children="node.children.map(n => graph.nodes_by_id[n])" :nodeid="nodeid"
|
const defaultScale = ref(1)
|
||||||
:boxWidth="boxWidth" :boxHeight="boxHeight" :marginX="marginX" :marginY="marginY"
|
const zoomTransform = ref(d3.zoomTransform({ k: 1, x: 0, y: 0 }))
|
||||||
:paddingX="paddingX"></viz-node>
|
|
||||||
</g>
|
const boxWidth = 300
|
||||||
</svg>
|
const boxHeight = 62
|
||||||
</div>
|
const paddingX = 50
|
||||||
</template>
|
const marginX = 50
|
||||||
<script>
|
const marginY = 20
|
||||||
export default {
|
|
||||||
components: {
|
interface GraphNode {
|
||||||
VizNode: VizNode.default,
|
rule: any
|
||||||
},
|
column: number
|
||||||
computed: {
|
children: string[]
|
||||||
boxWidth() {
|
y?: number
|
||||||
return 300
|
parent?: GraphNode
|
||||||
},
|
}
|
||||||
boxHeight() {
|
|
||||||
return 62
|
interface Graph {
|
||||||
},
|
nodes_by_id: Record<string, GraphNode>
|
||||||
paddingX() {
|
children: string[]
|
||||||
return 50
|
columns: number
|
||||||
},
|
height: number
|
||||||
marginX() {
|
y?: number
|
||||||
return 50
|
}
|
||||||
},
|
|
||||||
marginY() {
|
const graph = computed<Graph>(() => {
|
||||||
return 20
|
|
||||||
},
|
|
||||||
contentWidth() {
|
|
||||||
return this.graph.columns * (this.boxWidth + this.marginX) + 2 * this.paddingX
|
|
||||||
},
|
|
||||||
contentHeight() {
|
|
||||||
return this.graph.height * (this.boxHeight + this.marginY)
|
|
||||||
},
|
|
||||||
viewBox() {
|
|
||||||
return `0 0 ${this.contentWidth} ${this.contentHeight}`
|
|
||||||
},
|
|
||||||
graph() {
|
|
||||||
/**
|
/**
|
||||||
* Converts a JSON logic rule into a "flow chart".
|
* Converts a JSON logic rule into a "flow chart".
|
||||||
*
|
*
|
||||||
@@ -76,18 +64,19 @@ export default {
|
|||||||
* \ /
|
* \ /
|
||||||
* --- D ---
|
* --- D ---
|
||||||
*/
|
*/
|
||||||
const graph = {
|
const graphData: Graph = {
|
||||||
nodes_by_id: {},
|
nodes_by_id: {},
|
||||||
children: [],
|
children: [],
|
||||||
columns: -1,
|
columns: -1,
|
||||||
|
height: 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 1: Start building the graph by finding all nodes and edges
|
// Step 1: Start building the graph by finding all nodes and edges
|
||||||
let counter = 0;
|
let counter = 0
|
||||||
const _add_to_graph = (rule) => { // returns [heads, tails]
|
const _add_to_graph = (rule: any): [string[], string[]] => { // returns [heads, tails]
|
||||||
if (typeof rule !== 'object' || rule === null) {
|
if (typeof rule !== 'object' || rule === null) {
|
||||||
const node_id = (counter++).toString()
|
const node_id = (counter++).toString()
|
||||||
graph.nodes_by_id[node_id] = {
|
graphData.nodes_by_id[node_id] = {
|
||||||
rule: rule,
|
rule: rule,
|
||||||
column: -1,
|
column: -1,
|
||||||
children: [],
|
children: [],
|
||||||
@@ -98,16 +87,16 @@ export default {
|
|||||||
const operator = Object.keys(rule)[0]
|
const operator = Object.keys(rule)[0]
|
||||||
const operands = rule[operator]
|
const operands = rule[operator]
|
||||||
|
|
||||||
if (operator === "and") {
|
if (operator === 'and') {
|
||||||
let children = []
|
let children: string[] = []
|
||||||
let tails = null
|
let tails: string[] | null = null
|
||||||
operands.reverse()
|
operands.reverse()
|
||||||
for (let operand of operands) {
|
for (const operand of operands) {
|
||||||
let [new_children, new_tails] = _add_to_graph(operand)
|
const [new_children, new_tails] = _add_to_graph(operand)
|
||||||
for (let new_child of new_tails) {
|
for (const new_child of new_tails) {
|
||||||
graph.nodes_by_id[new_child].children.push(...children)
|
graphData.nodes_by_id[new_child].children.push(...children)
|
||||||
for (let c of children) {
|
for (const c of children) {
|
||||||
graph.nodes_by_id[c].parent = graph.nodes_by_id[new_child]
|
graphData.nodes_by_id[c].parent = graphData.nodes_by_id[new_child]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (tails === null) {
|
if (tails === null) {
|
||||||
@@ -115,141 +104,173 @@ export default {
|
|||||||
}
|
}
|
||||||
children = new_children
|
children = new_children
|
||||||
}
|
}
|
||||||
return [children, tails]
|
return [children, tails!]
|
||||||
} else if (operator === "or") {
|
} else if (operator === 'or') {
|
||||||
const children = []
|
const children: string[] = []
|
||||||
const tails = []
|
const tails: string[] = []
|
||||||
for (let operand of operands) {
|
for (const operand of operands) {
|
||||||
let [new_children, new_tails] = _add_to_graph(operand)
|
const [new_children, new_tails] = _add_to_graph(operand)
|
||||||
children.push(...new_children)
|
children.push(...new_children)
|
||||||
tails.push(...new_tails)
|
tails.push(...new_tails)
|
||||||
}
|
}
|
||||||
return [children, tails]
|
return [children, tails]
|
||||||
} else {
|
} else {
|
||||||
const node_id = (counter++).toString()
|
const node_id = (counter++).toString()
|
||||||
graph.nodes_by_id[node_id] = {
|
graphData.nodes_by_id[node_id] = {
|
||||||
rule: rule,
|
rule: rule,
|
||||||
column: -1,
|
column: -1,
|
||||||
children: [],
|
children: [],
|
||||||
}
|
}
|
||||||
return [[node_id], [node_id]]
|
return [[node_id], [node_id]]
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
graph.children = _add_to_graph(JSON.parse(JSON.stringify(this.$root.rules)))[0]
|
graphData.children = _add_to_graph(JSON.parse(JSON.stringify(rules.value)))[0]
|
||||||
|
|
||||||
// Step 2: We compute the "column" of every node, which is the maximum number of hops required to reach the
|
// Step 2: We compute the "column" of every node, which is the maximum number of hops required to reach the
|
||||||
// node from the root node
|
// node from the root node
|
||||||
const _set_column_to_min = (nodes, mincol) => {
|
const _set_column_to_min = (nodes: GraphNode[], mincol: number) => {
|
||||||
for (let node of nodes) {
|
for (const node of nodes) {
|
||||||
if (mincol > node.column) {
|
if (mincol > node.column) {
|
||||||
node.column = mincol
|
node.column = mincol
|
||||||
graph.columns = Math.max(mincol + 1, graph.columns)
|
graphData.columns = Math.max(mincol + 1, graphData.columns)
|
||||||
_set_column_to_min(node.children.map(nid => graph.nodes_by_id[nid]), mincol + 1)
|
_set_column_to_min(node.children.map(nid => graphData.nodes_by_id[nid]), mincol + 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_set_column_to_min(graph.children.map(nid => graph.nodes_by_id[nid]), 0)
|
_set_column_to_min(graphData.children.map(nid => graphData.nodes_by_id[nid]), 0)
|
||||||
|
|
||||||
// Step 3: Align each node on a grid. The x position is already given by the column computed above, but we still
|
// Step 3: Align each node on a grid. The x position is already given by the column computed above, but we still
|
||||||
// need the y position. This part of the algorithm is opinionated and probably not yet the nicest solution we
|
// need the y position. This part of the algorithm is opinionated and probably not yet the nicest solution we
|
||||||
// can use!
|
// can use!
|
||||||
const _set_y = (node, offset) => {
|
const _set_y = (node: Graph | GraphNode, offset: number): number => {
|
||||||
if (typeof node.y === "undefined") {
|
if (typeof node.y === 'undefined') {
|
||||||
// We only take the first value we found for each node
|
// We only take the first value we found for each node
|
||||||
node.y = offset
|
node.y = offset
|
||||||
}
|
}
|
||||||
|
|
||||||
let used = 0
|
let used = 0
|
||||||
for (let cid of node.children) {
|
for (const cid of node.children) {
|
||||||
used += Math.max(0, _set_y(graph.nodes_by_id[cid], offset + used) - 1)
|
used += Math.max(0, _set_y(graphData.nodes_by_id[cid], offset + used) - 1)
|
||||||
used++
|
used++
|
||||||
}
|
}
|
||||||
return used
|
return used
|
||||||
}
|
}
|
||||||
_set_y(graph, 0)
|
_set_y(graphData, 0)
|
||||||
|
|
||||||
// Step 4: Compute the "height" of the graph by looking at the node with the highest y value
|
// Step 4: Compute the "height" of the graph by looking at the node with the highest y value
|
||||||
graph.height = 1
|
graphData.height = 1
|
||||||
for (let node of [...Object.values(graph.nodes_by_id)]) {
|
for (const node of [...Object.values(graphData.nodes_by_id)]) {
|
||||||
graph.height = Math.max(graph.height, node.y + 1)
|
graphData.height = Math.max(graphData.height, (node.y ?? 0) + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
return graph
|
return graphData
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.createZoom()
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
window.addEventListener('resize', this.createZoom)
|
|
||||||
},
|
|
||||||
destroyed() {
|
|
||||||
window.removeEventListener('resize', this.createZoom)
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
maximized() {
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.createZoom()
|
|
||||||
})
|
})
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
createZoom() {
|
|
||||||
if (!this.$refs.svg) return
|
|
||||||
|
|
||||||
const viewportHeight = this.$refs.svg.clientHeight
|
const contentWidth = computed(() => {
|
||||||
const viewportWidth = this.$refs.svg.clientWidth
|
return graph.value.columns * (boxWidth + marginX) + 2 * paddingX
|
||||||
this.defaultScale = 1
|
})
|
||||||
|
|
||||||
this.zoom = d3
|
const contentHeight = computed(() => {
|
||||||
|
return graph.value.height * (boxHeight + marginY)
|
||||||
|
})
|
||||||
|
|
||||||
|
const viewBox = computed(() => {
|
||||||
|
return `0 0 ${contentWidth.value} ${contentHeight.value}`
|
||||||
|
})
|
||||||
|
|
||||||
|
function createZoom () {
|
||||||
|
if (!svg.value) return
|
||||||
|
|
||||||
|
const viewportHeight = svg.value.clientHeight
|
||||||
|
const viewportWidth = svg.value.clientWidth
|
||||||
|
defaultScale.value = 1
|
||||||
|
|
||||||
|
zoom.value = d3
|
||||||
.zoom()
|
.zoom()
|
||||||
.scaleExtent([Math.min(this.defaultScale * 0.5, 1), Math.max(5, this.contentHeight / viewportHeight, this.contentWidth / viewportWidth)])
|
.scaleExtent([Math.min(defaultScale.value * 0.5, 1), Math.max(5, contentHeight.value / viewportHeight, contentWidth.value / viewportWidth)])
|
||||||
.extent([[0, 0], [viewportWidth, viewportHeight]])
|
.extent([[0, 0], [viewportWidth, viewportHeight]])
|
||||||
.filter(event => {
|
.filter((event: any) => {
|
||||||
const wheeled = event.type === 'wheel'
|
const wheeled = event.type === 'wheel'
|
||||||
const mouseDrag =
|
const mouseDrag
|
||||||
event.type === 'mousedown' ||
|
= event.type === 'mousedown'
|
||||||
event.type === 'mouseup' ||
|
|| event.type === 'mouseup'
|
||||||
event.type === 'mousemove'
|
|| event.type === 'mousemove'
|
||||||
const touch =
|
const touch
|
||||||
event.type === 'touchstart' ||
|
= event.type === 'touchstart'
|
||||||
event.type === 'touchmove' ||
|
|| event.type === 'touchmove'
|
||||||
event.type === 'touchstop'
|
|| event.type === 'touchstop'
|
||||||
return (wheeled || mouseDrag || touch) && this.maximized
|
return (wheeled || mouseDrag || touch) && maximized.value
|
||||||
})
|
})
|
||||||
.wheelDelta(event => {
|
.wheelDelta((event: any) => {
|
||||||
// In contrast to default implementation, do not use a factor 10 if ctrl is pressed
|
// In contrast to default implementation, do not use a factor 10 if ctrl is pressed
|
||||||
return -event.deltaY * (event.deltaMode === 1 ? 0.05 : event.deltaMode ? 1 : 0.002)
|
return -event.deltaY * (event.deltaMode === 1 ? 0.05 : event.deltaMode ? 1 : 0.002)
|
||||||
})
|
})
|
||||||
.on('zoom', (event) => {
|
.on('zoom', (event: any) => {
|
||||||
this.zoomTransform = event.transform
|
zoomTransform.value = event.transform
|
||||||
})
|
})
|
||||||
|
|
||||||
const initTransform = d3.zoomIdentity
|
const initTransform = d3.zoomIdentity
|
||||||
.scale(this.defaultScale)
|
.scale(defaultScale.value)
|
||||||
.translate(
|
.translate(0, 0)
|
||||||
0,
|
zoomTransform.value = initTransform
|
||||||
0
|
|
||||||
)
|
|
||||||
this.zoomTransform = initTransform
|
|
||||||
|
|
||||||
// This sets correct d3 internal state for the initial centering
|
// This sets correct d3 internal state for the initial centering
|
||||||
d3.select(this.$refs.svg)
|
d3.select(svg.value)
|
||||||
.call(this.zoom.transform, initTransform)
|
.call(zoom.value.transform, initTransform)
|
||||||
|
|
||||||
const svg = d3.select(this.$refs.svg).call(this.zoom)
|
const svgSelection = d3.select(svg.value).call(zoom.value)
|
||||||
svg.on('touchmove.zoom', null)
|
svgSelection.on('touchmove.zoom', null)
|
||||||
// TODO touch support
|
// TODO touch support
|
||||||
},
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
maximized: false,
|
|
||||||
zoom: null,
|
|
||||||
defaultScale: 1,
|
|
||||||
zoomTransform: d3.zoomTransform({k: 1, x: 0, y: 0}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
watch(maximized, () => {
|
||||||
|
nextTick(() => {
|
||||||
|
createZoom()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
createZoom()
|
||||||
|
window.addEventListener('resize', createZoom)
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('resize', createZoom)
|
||||||
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
<template lang="pug">
|
||||||
|
div(:class="'checkin-rules-visualization ' + (maximized ? 'maximized' : '')")
|
||||||
|
.tools
|
||||||
|
button.btn.btn-default(
|
||||||
|
v-if="maximized",
|
||||||
|
type="button",
|
||||||
|
@click.prevent="maximized = false"
|
||||||
|
)
|
||||||
|
span.fa.fa-window-close
|
||||||
|
button.btn.btn-default(
|
||||||
|
v-if="!maximized",
|
||||||
|
type="button",
|
||||||
|
@click.prevent="maximized = true"
|
||||||
|
)
|
||||||
|
span.fa.fa-window-maximize
|
||||||
|
svg(
|
||||||
|
ref="svg",
|
||||||
|
:width="contentWidth",
|
||||||
|
:height="contentHeight",
|
||||||
|
:viewBox="viewBox"
|
||||||
|
)
|
||||||
|
g(:transform="zoomTransform.toString()")
|
||||||
|
VizNode(
|
||||||
|
v-for="(node, nodeid) in graph.nodes_by_id",
|
||||||
|
:key="nodeid",
|
||||||
|
:node="node",
|
||||||
|
:children="node.children.map((n: string) => graph.nodes_by_id[n])",
|
||||||
|
:nodeid="nodeid",
|
||||||
|
:boxWidth="boxWidth",
|
||||||
|
:boxHeight="boxHeight",
|
||||||
|
:marginX="marginX",
|
||||||
|
:marginY="marginY",
|
||||||
|
:paddingX="paddingX"
|
||||||
|
)
|
||||||
|
</template>
|
||||||
|
|||||||
193
src/pretix/static/pretixcontrol/js/ui/checkinrules/constants.ts
Normal file
193
src/pretix/static/pretixcontrol/js/ui/checkinrules/constants.ts
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
/* global gettext, pgettext */
|
||||||
|
|
||||||
|
export const TEXTS = {
|
||||||
|
and: gettext('All of the conditions below (AND)'),
|
||||||
|
or: gettext('At least one of the conditions below (OR)'),
|
||||||
|
date_from: gettext('Event start'),
|
||||||
|
date_to: gettext('Event end'),
|
||||||
|
date_admission: gettext('Event admission'),
|
||||||
|
date_custom: gettext('custom date and time'),
|
||||||
|
date_customtime: gettext('custom time'),
|
||||||
|
date_tolerance: gettext('Tolerance (minutes)'),
|
||||||
|
condition_add: gettext('Add condition'),
|
||||||
|
minutes: gettext('minutes'),
|
||||||
|
duplicate: gettext('Duplicate'),
|
||||||
|
status_present: pgettext('entry_status', 'present'),
|
||||||
|
status_absent: pgettext('entry_status', 'absent'),
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TYPEOPS = {
|
||||||
|
// Every change to our supported JSON logic must be done
|
||||||
|
// * in pretix.base.services.checkin
|
||||||
|
// * in pretix.base.models.checkin
|
||||||
|
// * in pretix.helpers.jsonlogic_boolalg
|
||||||
|
// * in checkinrules.js
|
||||||
|
// * in libpretixsync
|
||||||
|
// * in pretixscan-ios
|
||||||
|
product: {
|
||||||
|
inList: {
|
||||||
|
label: gettext('is one of'),
|
||||||
|
cardinality: 2,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
variation: {
|
||||||
|
inList: {
|
||||||
|
label: gettext('is one of'),
|
||||||
|
cardinality: 2,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
gate: {
|
||||||
|
inList: {
|
||||||
|
label: gettext('is one of'),
|
||||||
|
cardinality: 2,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
datetime: {
|
||||||
|
isBefore: {
|
||||||
|
label: gettext('is before'),
|
||||||
|
cardinality: 2,
|
||||||
|
},
|
||||||
|
isAfter: {
|
||||||
|
label: gettext('is after'),
|
||||||
|
cardinality: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
enum_entry_status: {
|
||||||
|
'==': {
|
||||||
|
label: gettext('='),
|
||||||
|
cardinality: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
int_by_datetime: {
|
||||||
|
'<': {
|
||||||
|
label: '<',
|
||||||
|
cardinality: 2,
|
||||||
|
},
|
||||||
|
'<=': {
|
||||||
|
label: '≤',
|
||||||
|
cardinality: 2,
|
||||||
|
},
|
||||||
|
'>': {
|
||||||
|
label: '>',
|
||||||
|
cardinality: 2,
|
||||||
|
},
|
||||||
|
'>=': {
|
||||||
|
label: '≥',
|
||||||
|
cardinality: 2,
|
||||||
|
},
|
||||||
|
'==': {
|
||||||
|
label: '=',
|
||||||
|
cardinality: 2,
|
||||||
|
},
|
||||||
|
'!=': {
|
||||||
|
label: '≠',
|
||||||
|
cardinality: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
int: {
|
||||||
|
'<': {
|
||||||
|
label: '<',
|
||||||
|
cardinality: 2,
|
||||||
|
},
|
||||||
|
'<=': {
|
||||||
|
label: '≤',
|
||||||
|
cardinality: 2,
|
||||||
|
},
|
||||||
|
'>': {
|
||||||
|
label: '>',
|
||||||
|
cardinality: 2,
|
||||||
|
},
|
||||||
|
'>=': {
|
||||||
|
label: '≥',
|
||||||
|
cardinality: 2,
|
||||||
|
},
|
||||||
|
'==': {
|
||||||
|
label: '=',
|
||||||
|
cardinality: 2,
|
||||||
|
},
|
||||||
|
'!=': {
|
||||||
|
label: '≠',
|
||||||
|
cardinality: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const VARS = {
|
||||||
|
product: {
|
||||||
|
label: gettext('Product'),
|
||||||
|
type: 'product',
|
||||||
|
},
|
||||||
|
variation: {
|
||||||
|
label: gettext('Product variation'),
|
||||||
|
type: 'variation',
|
||||||
|
},
|
||||||
|
gate: {
|
||||||
|
label: gettext('Gate'),
|
||||||
|
type: 'gate',
|
||||||
|
},
|
||||||
|
now: {
|
||||||
|
label: gettext('Current date and time'),
|
||||||
|
type: 'datetime',
|
||||||
|
},
|
||||||
|
now_isoweekday: {
|
||||||
|
label: gettext('Current day of the week (1 = Monday, 7 = Sunday)'),
|
||||||
|
type: 'int',
|
||||||
|
},
|
||||||
|
entry_status: {
|
||||||
|
label: gettext('Current entry status'),
|
||||||
|
type: 'enum_entry_status',
|
||||||
|
},
|
||||||
|
entries_number: {
|
||||||
|
label: gettext('Number of previous entries'),
|
||||||
|
type: 'int',
|
||||||
|
},
|
||||||
|
entries_today: {
|
||||||
|
label: gettext('Number of previous entries since midnight'),
|
||||||
|
type: 'int',
|
||||||
|
},
|
||||||
|
entries_since: {
|
||||||
|
label: gettext('Number of previous entries since'),
|
||||||
|
type: 'int_by_datetime',
|
||||||
|
},
|
||||||
|
entries_before: {
|
||||||
|
label: gettext('Number of previous entries before'),
|
||||||
|
type: 'int_by_datetime',
|
||||||
|
},
|
||||||
|
entries_days: {
|
||||||
|
label: gettext('Number of days with a previous entry'),
|
||||||
|
type: 'int',
|
||||||
|
},
|
||||||
|
entries_days_since: {
|
||||||
|
label: gettext('Number of days with a previous entry since'),
|
||||||
|
type: 'int_by_datetime',
|
||||||
|
},
|
||||||
|
entries_days_before: {
|
||||||
|
label: gettext('Number of days with a previous entry before'),
|
||||||
|
type: 'int_by_datetime',
|
||||||
|
},
|
||||||
|
minutes_since_last_entry: {
|
||||||
|
label: gettext('Minutes since last entry (-1 on first entry)'),
|
||||||
|
type: 'int',
|
||||||
|
},
|
||||||
|
minutes_since_first_entry: {
|
||||||
|
label: gettext('Minutes since first entry (-1 on first entry)'),
|
||||||
|
type: 'int',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DATETIME_OPTIONS = {
|
||||||
|
format: document.body.dataset.datetimeformat,
|
||||||
|
locale: document.body.dataset.datetimelocale,
|
||||||
|
useCurrent: false,
|
||||||
|
icons: {
|
||||||
|
time: 'fa fa-clock-o',
|
||||||
|
date: 'fa fa-calendar',
|
||||||
|
up: 'fa fa-chevron-up',
|
||||||
|
down: 'fa fa-chevron-down',
|
||||||
|
previous: 'fa fa-chevron-left',
|
||||||
|
next: 'fa fa-chevron-right',
|
||||||
|
today: 'fa fa-screenshot',
|
||||||
|
clear: 'fa fa-trash',
|
||||||
|
close: 'fa fa-remove'
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,55 +1,45 @@
|
|||||||
<template>
|
<script setup lang="ts">
|
||||||
<input class="form-control">
|
import { ref, watch, onMounted, onUnmounted } from 'vue'
|
||||||
</template>
|
import { DATETIME_OPTIONS } from './constants'
|
||||||
<script>
|
|
||||||
export default {
|
const props = defineProps<{
|
||||||
props: ["required", "value"],
|
required?: boolean
|
||||||
template: (''),
|
value?: string
|
||||||
mounted: function () {
|
}>()
|
||||||
var vm = this;
|
|
||||||
var multiple = this.multiple;
|
const emit = defineEmits<{
|
||||||
$(this.$el)
|
input: [value: string]
|
||||||
.datetimepicker(this.opts())
|
}>()
|
||||||
.trigger("change")
|
|
||||||
.on("dp.change", function (e) {
|
const input = ref<HTMLInputElement | null>(null)
|
||||||
vm.$emit("input", $(this).data('DateTimePicker').date().toISOString());
|
|
||||||
});
|
watch(() => props.value, (val) => {
|
||||||
if (!vm.value) {
|
$(input.value).data('DateTimePicker').date(moment(val))
|
||||||
$(this.$el).data("DateTimePicker").viewDate(moment().hour(0).minute(0).second(0).millisecond(0));
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
$(input.value)
|
||||||
|
.datetimepicker({
|
||||||
|
...DATETIME_OPTIONS,
|
||||||
|
showClear: props.required,
|
||||||
|
})
|
||||||
|
.trigger('change')
|
||||||
|
.on('dp.change', function (this: HTMLElement) {
|
||||||
|
emit('input', $(this).data('DateTimePicker').date().toISOString())
|
||||||
|
})
|
||||||
|
if (!props.value) {
|
||||||
|
$(input.value).data('DateTimePicker').viewDate(moment().hour(0).minute(0).second(0).millisecond(0))
|
||||||
} else {
|
} else {
|
||||||
$(this.$el).data("DateTimePicker").date(moment(vm.value));
|
$(input.value).data('DateTimePicker').date(moment(props.value))
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
methods: {
|
|
||||||
opts: function () {
|
onUnmounted(() => {
|
||||||
return {
|
$(input.value)
|
||||||
format: $("body").attr("data-datetimeformat"),
|
|
||||||
locale: $("body").attr("data-datetimelocale"),
|
|
||||||
useCurrent: false,
|
|
||||||
showClear: this.required,
|
|
||||||
icons: {
|
|
||||||
time: 'fa fa-clock-o',
|
|
||||||
date: 'fa fa-calendar',
|
|
||||||
up: 'fa fa-chevron-up',
|
|
||||||
down: 'fa fa-chevron-down',
|
|
||||||
previous: 'fa fa-chevron-left',
|
|
||||||
next: 'fa fa-chevron-right',
|
|
||||||
today: 'fa fa-screenshot',
|
|
||||||
clear: 'fa fa-trash',
|
|
||||||
close: 'fa fa-remove'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
value: function (val) {
|
|
||||||
$(this.$el).data('DateTimePicker').date(moment(val));
|
|
||||||
},
|
|
||||||
},
|
|
||||||
destroyed: function () {
|
|
||||||
$(this.$el)
|
|
||||||
.off()
|
.off()
|
||||||
.datetimepicker("destroy");
|
.datetimepicker('destroy')
|
||||||
}
|
})
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
<template lang="pug">
|
||||||
|
input.form-control(ref="input")
|
||||||
|
</template>
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import { ref, watch } from 'vue'
|
||||||
|
|
||||||
|
export const allProducts = ref(false)
|
||||||
|
export const limitProducts = ref<number[]>([])
|
||||||
|
|
||||||
|
function updateProducts () {
|
||||||
|
allProducts.value = document.querySelector<HTMLInputElement>('#id_all_products')?.checked ?? false
|
||||||
|
limitProducts.value = Array.from(document.querySelectorAll<HTMLInputElement>('input[name=limit_products]:checked')).map(el => parseInt(el.value))
|
||||||
|
}
|
||||||
|
|
||||||
|
// listen to change events for products
|
||||||
|
document.querySelectorAll('#id_all_products, input[name=limit_products]').forEach(el => el.addEventListener('change', updateProducts))
|
||||||
|
updateProducts()
|
||||||
|
|
||||||
|
export const rules = ref<any>({})
|
||||||
|
|
||||||
|
// grab rules from hidden input
|
||||||
|
const rulesInput = document.querySelector<HTMLInputElement>('#id_rules')
|
||||||
|
if (rulesInput?.value) {
|
||||||
|
rules.value = JSON.parse(rulesInput.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sync back to hidden input
|
||||||
|
watch(rules, (newVal) => {
|
||||||
|
if (!rulesInput) return
|
||||||
|
rulesInput.value = JSON.stringify(newVal)
|
||||||
|
}, { deep: true })
|
||||||
|
|
||||||
|
export const items = ref<any[]>([])
|
||||||
|
|
||||||
|
const itemsEl = document.querySelector('#items')
|
||||||
|
if (itemsEl?.textContent) {
|
||||||
|
items.value = JSON.parse(itemsEl.textContent || '[]')
|
||||||
|
|
||||||
|
function checkForInvalidIds (validProducts: Record<string, string>, validVariations: Record<string, string>, rule: any) {
|
||||||
|
if (rule['and']) {
|
||||||
|
for (const child of rule['and'])
|
||||||
|
checkForInvalidIds(validProducts, validVariations, child)
|
||||||
|
} else if (rule['or']) {
|
||||||
|
for (const child of rule['or'])
|
||||||
|
checkForInvalidIds(validProducts, validVariations, child)
|
||||||
|
} else if (rule['inList'] && rule['inList'][0]['var'] === 'product') {
|
||||||
|
for (const item of rule['inList'][1]['objectList']) {
|
||||||
|
if (!validProducts[item['lookup'][1]])
|
||||||
|
item['lookup'][2] = '[' + gettext('Error: Product not found!') + ']'
|
||||||
|
else
|
||||||
|
item['lookup'][2] = validProducts[item['lookup'][1]]
|
||||||
|
}
|
||||||
|
} else if (rule['inList'] && rule['inList'][0]['var'] === 'variation') {
|
||||||
|
for (const item of rule['inList'][1]['objectList']) {
|
||||||
|
if (!validVariations[item['lookup'][1]])
|
||||||
|
item['lookup'][2] = '[' + gettext('Error: Variation not found!') + ']'
|
||||||
|
else
|
||||||
|
item['lookup'][2] = validVariations[item['lookup'][1]]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkForInvalidIds(
|
||||||
|
Object.fromEntries(items.value.map(p => [p.id, p.name])),
|
||||||
|
Object.fromEntries(items.value.flatMap(p => p.variations?.map(v => [v.id, p.name + ' – ' + v.name]) ?? [])),
|
||||||
|
rules.value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const productSelectURL = ref(document.querySelector('#product-select2')?.textContent)
|
||||||
|
export const variationSelectURL = ref(document.querySelector('#variations-select2')?.textContent)
|
||||||
|
export const gateSelectURL = ref(document.querySelector('#gate-select2')?.textContent)
|
||||||
12
src/pretix/static/pretixcontrol/js/ui/checkinrules/index.ts
Normal file
12
src/pretix/static/pretixcontrol/js/ui/checkinrules/index.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { createApp } from 'vue'
|
||||||
|
import App from './App.vue'
|
||||||
|
|
||||||
|
const app = createApp(App)
|
||||||
|
app.mount('#rules-editor')
|
||||||
|
|
||||||
|
app.config.errorHandler = (error, _vm, info) => {
|
||||||
|
// vue fatals on errors by default, which is a weird choice
|
||||||
|
// https://github.com/vuejs/core/issues/3525
|
||||||
|
// https://github.com/vuejs/router/discussions/2435
|
||||||
|
console.error('[VUE]', info, error)
|
||||||
|
}
|
||||||
@@ -1,19 +1,19 @@
|
|||||||
function convert_to_dnf(rules) {
|
export function convertToDNF (rules) {
|
||||||
// Converts a set of rules to disjunctive normal form, i.e. returns something of the form
|
// Converts a set of rules to disjunctive normal form, i.e. returns something of the form
|
||||||
// `(a AND b AND c) OR (a AND d AND f)`
|
// `(a AND b AND c) OR (a AND d AND f)`
|
||||||
// without further nesting.
|
// without further nesting.
|
||||||
if (typeof rules !== "object" || Array.isArray(rules) || rules === null) {
|
if (typeof rules !== 'object' || Array.isArray(rules) || rules === null) {
|
||||||
return rules
|
return rules
|
||||||
}
|
}
|
||||||
|
|
||||||
function _distribute_or_over_and (r) {
|
function _distribute_or_over_and (r) {
|
||||||
var operator = Object.keys(r)[0]
|
let operator = Object.keys(r)[0]
|
||||||
var values = r[operator]
|
let values = r[operator]
|
||||||
if (operator === "and") {
|
if (operator === 'and') {
|
||||||
var arg_to_distribute = null
|
let arg_to_distribute = null
|
||||||
var other_args = []
|
let other_args = []
|
||||||
for (var arg of values) {
|
for (let arg of values) {
|
||||||
if (typeof arg === "object" && !Array.isArray(arg) && typeof arg["or"] !== "undefined" && arg_to_distribute === null) {
|
if (typeof arg === 'object' && !Array.isArray(arg) && typeof arg['or'] !== 'undefined' && arg_to_distribute === null) {
|
||||||
arg_to_distribute = arg
|
arg_to_distribute = arg
|
||||||
} else {
|
} else {
|
||||||
other_args.push(arg)
|
other_args.push(arg)
|
||||||
@@ -22,17 +22,17 @@ function convert_to_dnf(rules) {
|
|||||||
if (arg_to_distribute === null) {
|
if (arg_to_distribute === null) {
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
var or_operands = []
|
let or_operands = []
|
||||||
for (var dval of arg_to_distribute["or"]) {
|
for (let dval of arg_to_distribute['or']) {
|
||||||
or_operands.push({"and": other_args.concat([dval])})
|
or_operands.push({ and: other_args.concat([dval]) })
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
"or": or_operands
|
or: or_operands
|
||||||
}
|
}
|
||||||
} else if (!operator) {
|
} else if (!operator) {
|
||||||
return r
|
return r
|
||||||
} else if (operator === "!" || operator === "!!" || operator === "?:" || operator === "if") {
|
} else if (operator === '!' || operator === '!!' || operator === '?:' || operator === 'if') {
|
||||||
console.warn("Operator " + operator + " currently unsupported by convert_to_dnf")
|
console.warn('Operator ' + operator + ' currently unsupported by convert_to_dnf')
|
||||||
return r
|
return r
|
||||||
} else {
|
} else {
|
||||||
return r
|
return r
|
||||||
@@ -41,35 +41,35 @@ function convert_to_dnf(rules) {
|
|||||||
|
|
||||||
function _simplify_chained_operators (r) {
|
function _simplify_chained_operators (r) {
|
||||||
// Simplify `(a OR b) OR (c or d)` to `a OR b OR c OR d` and the same with `AND`
|
// Simplify `(a OR b) OR (c or d)` to `a OR b OR c OR d` and the same with `AND`
|
||||||
if (typeof r !== "object" || Array.isArray(r)) {
|
if (typeof r !== 'object' || Array.isArray(r)) {
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
var operator = Object.keys(r)[0]
|
let operator = Object.keys(r)[0]
|
||||||
var values = r[operator]
|
let values = r[operator]
|
||||||
if (operator !== "or" && operator !== "and") {
|
if (operator !== 'or' && operator !== 'and') {
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
var new_values = []
|
let new_values = []
|
||||||
for (var v of values) {
|
for (let v of values) {
|
||||||
if (typeof v !== "object" || Array.isArray(v) || typeof v[operator] === "undefined") {
|
if (typeof v !== 'object' || Array.isArray(v) || typeof v[operator] === 'undefined') {
|
||||||
new_values.push(v)
|
new_values.push(v)
|
||||||
} else {
|
} else {
|
||||||
new_values.push(...v[operator])
|
new_values.push(...v[operator])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var result = {}
|
let result = {}
|
||||||
result[operator] = new_values
|
result[operator] = new_values
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run _distribute_or_over_and on until it no longer changes anything. Do so recursively
|
// Run _distribute_or_over_and on until it no longer changes anything. Do so recursively
|
||||||
// for the full expression tree.
|
// for the full expression tree.
|
||||||
var old_rules = rules
|
let old_rules = rules
|
||||||
while (true) {
|
while (true) {
|
||||||
rules = _distribute_or_over_and(rules)
|
rules = _distribute_or_over_and(rules)
|
||||||
var operator = Object.keys(rules)[0]
|
let operator = Object.keys(rules)[0]
|
||||||
var values = rules[operator]
|
let values = rules[operator]
|
||||||
var no_list = false
|
let no_list = false
|
||||||
if (!Array.isArray(values)) {
|
if (!Array.isArray(values)) {
|
||||||
values = [values]
|
values = [values]
|
||||||
no_list = true
|
no_list = true
|
||||||
@@ -77,11 +77,11 @@ function convert_to_dnf(rules) {
|
|||||||
rules = {}
|
rules = {}
|
||||||
if (!no_list) {
|
if (!no_list) {
|
||||||
rules[operator] = []
|
rules[operator] = []
|
||||||
for (var v of values) {
|
for (let v of values) {
|
||||||
rules[operator].push(convert_to_dnf(v))
|
rules[operator].push(convertToDNF(v))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
rules[operator] = convert_to_dnf(values[0])
|
rules[operator] = convertToDNF(values[0])
|
||||||
}
|
}
|
||||||
if (JSON.stringify(old_rules) === JSON.stringify(rules)) { // Let's hope this is good enough...
|
if (JSON.stringify(old_rules) === JSON.stringify(rules)) { // Let's hope this is good enough...
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -1,97 +1,116 @@
|
|||||||
<template>
|
<script setup lang="ts">
|
||||||
<select>
|
import { ref, watch, onMounted, onUnmounted } from 'vue'
|
||||||
<slot></slot>
|
|
||||||
</select>
|
declare const $: any
|
||||||
</template>
|
|
||||||
<script>
|
export interface ObjectListItem {
|
||||||
export default {
|
lookup: [string, number | string, string]
|
||||||
props: ["required", "value", "placeholder", "url", "multiple"],
|
|
||||||
template: ('<select>\n' +
|
|
||||||
' <slot></slot>\n' +
|
|
||||||
' </select>'),
|
|
||||||
mounted: function () {
|
|
||||||
this.build();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
build: function () {
|
|
||||||
var vm = this;
|
|
||||||
var multiple = this.multiple;
|
|
||||||
$(this.$el)
|
|
||||||
.empty()
|
|
||||||
.select2(this.opts())
|
|
||||||
.val(this.value || "")
|
|
||||||
.trigger("change")
|
|
||||||
// emit event on change.
|
|
||||||
.on("change", function (e) {
|
|
||||||
vm.$emit("input", $(this).select2('data'));
|
|
||||||
});
|
|
||||||
if (vm.value) {
|
|
||||||
for (var i = 0; i < vm.value["objectList"].length; i++) {
|
|
||||||
var option = new Option(vm.value["objectList"][i]["lookup"][2], vm.value["objectList"][i]["lookup"][1], true, true);
|
|
||||||
$(vm.$el).append(option);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ObjectList {
|
||||||
|
objectList: ObjectListItem[]
|
||||||
}
|
}
|
||||||
$(vm.$el).trigger("change");
|
|
||||||
},
|
const props = defineProps<{
|
||||||
opts: function () {
|
required?: boolean
|
||||||
|
value?: ObjectList
|
||||||
|
placeholder?: string
|
||||||
|
url?: string
|
||||||
|
multiple?: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
input: [value: any[]]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const select = ref<HTMLSelectElement | null>(null)
|
||||||
|
|
||||||
|
function opts () {
|
||||||
return {
|
return {
|
||||||
theme: "bootstrap",
|
theme: 'bootstrap',
|
||||||
delay: 100,
|
delay: 100,
|
||||||
width: '100%',
|
width: '100%',
|
||||||
multiple: true,
|
multiple: true,
|
||||||
allowClear: this.required,
|
allowClear: props.required,
|
||||||
language: $("body").attr("data-select2-locale"),
|
language: $('body').attr('data-select2-locale'),
|
||||||
ajax: {
|
ajax: {
|
||||||
url: this.url,
|
url: props.url,
|
||||||
data: function (params) {
|
data: function (params: { term: string; page?: number }) {
|
||||||
return {
|
return {
|
||||||
query: params.term,
|
query: params.term,
|
||||||
page: params.page || 1
|
page: params.page || 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
templateResult: function (res) {
|
templateResult: function (res: { id?: string; text: string }) {
|
||||||
if (!res.id) {
|
if (!res.id) {
|
||||||
return res.text;
|
return res.text
|
||||||
}
|
}
|
||||||
var $ret = $("<span>").append(
|
const $ret = $('<span>').append(
|
||||||
$("<span>").addClass("primary").append($("<div>").text(res.text).html())
|
$('<span>').addClass('primary').append($('<div>').text(res.text).html())
|
||||||
);
|
)
|
||||||
return $ret;
|
return $ret
|
||||||
},
|
},
|
||||||
};
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
watch: {
|
|
||||||
placeholder: function (val) {
|
function build () {
|
||||||
$(this.$el).select2("destroy");
|
$(select.value)
|
||||||
this.build();
|
.empty()
|
||||||
},
|
.select2(opts())
|
||||||
required: function (val) {
|
.val(props.value || '')
|
||||||
$(this.$el).select2("destroy");
|
.trigger('change')
|
||||||
this.build();
|
.on('change', function (this: HTMLElement) {
|
||||||
},
|
emit('input', $(this).select2('data'))
|
||||||
url: function (val) {
|
})
|
||||||
$(this.$el).select2("destroy");
|
if (props.value) {
|
||||||
this.build();
|
for (let i = 0; i < props.value.objectList.length; i++) {
|
||||||
},
|
const option = new Option(props.value.objectList[i].lookup[2], String(props.value.objectList[i].lookup[1]), true, true)
|
||||||
value: function (newval, oldval) {
|
$(select.value).append(option)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$(select.value).trigger('change')
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => props.placeholder, () => {
|
||||||
|
$(select.value).select2('destroy')
|
||||||
|
build()
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => props.required, () => {
|
||||||
|
$(select.value).select2('destroy')
|
||||||
|
build()
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => props.url, () => {
|
||||||
|
$(select.value).select2('destroy')
|
||||||
|
build()
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => props.value, (newval, oldval) => {
|
||||||
if (JSON.stringify(newval) !== JSON.stringify(oldval)) {
|
if (JSON.stringify(newval) !== JSON.stringify(oldval)) {
|
||||||
$(this.$el).empty();
|
$(select.value).empty()
|
||||||
if (newval) {
|
if (newval) {
|
||||||
for (var i = 0; i < newval["objectList"].length; i++) {
|
for (let i = 0; i < newval.objectList.length; i++) {
|
||||||
var option = new Option(newval["objectList"][i]["lookup"][2], newval["objectList"][i]["lookup"][1], true, true);
|
const option = new Option(newval.objectList[i].lookup[2], String(newval.objectList[i].lookup[1]), true, true)
|
||||||
$(this.$el).append(option);
|
$(select.value).append(option)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$(this.$el).trigger("change");
|
$(select.value).trigger('change')
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
},
|
|
||||||
destroyed: function () {
|
onMounted(() => {
|
||||||
$(this.$el)
|
build()
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
$(select.value)
|
||||||
.off()
|
.off()
|
||||||
.select2("destroy");
|
.select2('destroy')
|
||||||
}
|
})
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
<template lang="pug">
|
||||||
|
select(ref="select")
|
||||||
|
slot
|
||||||
|
</template>
|
||||||
|
|||||||
@@ -1,55 +1,45 @@
|
|||||||
<template>
|
<script setup lang="ts">
|
||||||
<input class="form-control">
|
import { ref, watch, onMounted, onUnmounted } from 'vue'
|
||||||
</template>
|
import { DATETIME_OPTIONS } from './constants'
|
||||||
<script>
|
|
||||||
export default {
|
const props = defineProps<{
|
||||||
props: ["required", "value"],
|
required?: boolean
|
||||||
template: (''),
|
value?: string
|
||||||
mounted: function () {
|
}>()
|
||||||
var vm = this;
|
|
||||||
var multiple = this.multiple;
|
const emit = defineEmits<{
|
||||||
$(this.$el)
|
input: [value: string]
|
||||||
.datetimepicker(this.opts())
|
}>()
|
||||||
.trigger("change")
|
|
||||||
.on("dp.change", function (e) {
|
const input = ref<HTMLInputElement | null>(null)
|
||||||
vm.$emit("input", $(this).data('DateTimePicker').date().format("HH:mm:ss"));
|
|
||||||
});
|
watch(() => props.value, (val) => {
|
||||||
if (!vm.value) {
|
$(input.value).data('DateTimePicker').date(val)
|
||||||
$(this.$el).data("DateTimePicker").viewDate(moment().hour(0).minute(0).second(0).millisecond(0));
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
$(input.value)
|
||||||
|
.datetimepicker({
|
||||||
|
...DATETIME_OPTIONS,
|
||||||
|
showClear: props.required,
|
||||||
|
})
|
||||||
|
.trigger('change')
|
||||||
|
.on('dp.change', function (this: HTMLElement) {
|
||||||
|
emit('input', $(this).data('DateTimePicker').date().format('HH:mm:ss'))
|
||||||
|
})
|
||||||
|
if (!props.value) {
|
||||||
|
$(input.value).data('DateTimePicker').viewDate(moment().hour(0).minute(0).second(0).millisecond(0))
|
||||||
} else {
|
} else {
|
||||||
$(this.$el).data("DateTimePicker").date(vm.value);
|
$(input.value).data('DateTimePicker').date(props.value)
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
methods: {
|
|
||||||
opts: function () {
|
onUnmounted(() => {
|
||||||
return {
|
$(input.value)
|
||||||
format: $("body").attr("data-timeformat"),
|
|
||||||
locale: $("body").attr("data-datetimelocale"),
|
|
||||||
useCurrent: false,
|
|
||||||
showClear: this.required,
|
|
||||||
icons: {
|
|
||||||
time: 'fa fa-clock-o',
|
|
||||||
date: 'fa fa-calendar',
|
|
||||||
up: 'fa fa-chevron-up',
|
|
||||||
down: 'fa fa-chevron-down',
|
|
||||||
previous: 'fa fa-chevron-left',
|
|
||||||
next: 'fa fa-chevron-right',
|
|
||||||
today: 'fa fa-screenshot',
|
|
||||||
clear: 'fa fa-trash',
|
|
||||||
close: 'fa fa-remove'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
value: function (val) {
|
|
||||||
$(this.$el).data('DateTimePicker').date(val);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
destroyed: function () {
|
|
||||||
$(this.$el)
|
|
||||||
.off()
|
.off()
|
||||||
.datetimepicker("destroy");
|
.datetimepicker('destroy')
|
||||||
}
|
})
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
<template lang="pug">
|
||||||
|
input.form-control(ref="input")
|
||||||
|
</template>
|
||||||
|
|||||||
@@ -1,140 +1,43 @@
|
|||||||
<template>
|
<script setup lang="ts">
|
||||||
<g>
|
import { computed } from 'vue'
|
||||||
<path v-for="e in edges" :d="e" class="edge"></path>
|
import { TEXTS, VARS, TYPEOPS } from './constants'
|
||||||
<path v-if="rootEdge" :d="rootEdge" class="edge"></path>
|
|
||||||
<path v-if="!node.children.length" :d="checkEdge" class="edge"></path>
|
|
||||||
<rect :width="boxWidth" :height="boxHeight" :x="x" :y="y" :class="nodeClass" rx="5">
|
|
||||||
</rect>
|
|
||||||
|
|
||||||
<foreignObject :width="boxWidth - 10" :height="boxHeight - 10" :x="x + 5" :y="y + 5">
|
declare const $: any
|
||||||
<div xmlns="http://www.w3.org/1999/xhtml" class="text">
|
declare const moment: any
|
||||||
<span v-if="vardata && vardata.type === 'int'">
|
|
||||||
<span v-if="variable.startsWith('entries_')" class="fa fa-sign-in"></span>
|
|
||||||
{{ vardata.label }}
|
|
||||||
<br>
|
|
||||||
<span v-if="varresult !== null">
|
|
||||||
{{varresult}}
|
|
||||||
</span>
|
|
||||||
<strong>
|
|
||||||
{{ op.label }} {{ rightoperand }}
|
|
||||||
</strong>
|
|
||||||
</span>
|
|
||||||
<span v-else-if="vardata && vardata.type === 'int_by_datetime'">
|
|
||||||
<span v-if="variable.startsWith('entries_')" class="fa fa-sign-in"></span>
|
|
||||||
{{ vardata.label }}
|
|
||||||
<span v-if="node.rule[operator][0][variable][0].buildTime[0] === 'custom'">
|
|
||||||
{{ df(node.rule[operator][0][variable][0].buildTime[1]) }}
|
|
||||||
</span>
|
|
||||||
<span v-else-if="node.rule[operator][0][variable][0].buildTime[0] === 'customtime'">
|
|
||||||
{{ tf(node.rule[operator][0][variable][0].buildTime[1]) }}
|
|
||||||
</span>
|
|
||||||
<span v-else>
|
|
||||||
{{ this.$root.texts[node.rule[operator][0][variable][0].buildTime[0]] }}
|
|
||||||
</span>
|
|
||||||
<br>
|
|
||||||
<span v-if="varresult !== null">
|
|
||||||
{{varresult}}
|
|
||||||
</span>
|
|
||||||
<strong>
|
|
||||||
{{ op.label }} {{ rightoperand }}
|
|
||||||
</strong>
|
|
||||||
</span>
|
|
||||||
<span v-else-if="vardata && variable === 'now'">
|
|
||||||
<span class="fa fa-clock-o"></span> {{ vardata.label }}<br>
|
|
||||||
<span v-if="varresult !== null">
|
|
||||||
{{varresult}}
|
|
||||||
</span>
|
|
||||||
<strong>
|
|
||||||
{{ op.label }}<br>
|
|
||||||
<span v-if="rightoperand.buildTime[0] === 'custom'">
|
|
||||||
{{ df(rightoperand.buildTime[1]) }}
|
|
||||||
</span>
|
|
||||||
<span v-else-if="rightoperand.buildTime[0] === 'customtime'">
|
|
||||||
{{ tf(rightoperand.buildTime[1]) }}
|
|
||||||
</span>
|
|
||||||
<span v-else>
|
|
||||||
{{ this.$root.texts[rightoperand.buildTime[0]] }}
|
|
||||||
</span>
|
|
||||||
<span v-if="operands[2]">
|
|
||||||
<span v-if="operator === 'isBefore'">+</span>
|
|
||||||
<span v-else>-</span>
|
|
||||||
{{ operands[2] }}
|
|
||||||
{{ this.$root.texts.minutes }}
|
|
||||||
</span>
|
|
||||||
</strong>
|
|
||||||
</span>
|
|
||||||
<span v-else-if="vardata && operator === 'inList'">
|
|
||||||
<span class="fa fa-sign-in" v-if="variable === 'gate'"></span>
|
|
||||||
<span class="fa fa-ticket" v-else></span>
|
|
||||||
{{ vardata.label }}
|
|
||||||
<span v-if="varresult !== null">
|
|
||||||
({{varresult}})
|
|
||||||
</span>
|
|
||||||
<br>
|
|
||||||
<strong>
|
|
||||||
{{ rightoperand.objectList.map((o) => o.lookup[2]).join(", ") }}
|
|
||||||
</strong>
|
|
||||||
</span>
|
|
||||||
<span v-else-if="vardata && vardata.type === 'enum_entry_status'">
|
|
||||||
<span class="fa fa-check-circle-o"></span>
|
|
||||||
{{ vardata.label }}
|
|
||||||
<span v-if="varresult !== null">
|
|
||||||
({{varresult}})
|
|
||||||
</span>
|
|
||||||
<br>
|
|
||||||
<strong>
|
|
||||||
{{ op.label }} {{ rightoperand }}
|
|
||||||
</strong>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</foreignObject>
|
|
||||||
|
|
||||||
<g v-if="result === false" :transform="`translate(${x + boxWidth - 15}, ${y - 10})`">
|
interface GraphNode {
|
||||||
<ellipse fill="#fff" cx="14.685823" cy="14.318233" rx="12.140151" ry="11.55523" />
|
rule: any
|
||||||
<path d="M 15,0 C 23.28125,0 30,6.71875 30,15 30,23.28125 23.28125,30 15,30 6.71875,30 0,23.28125 0,15 0,6.71875 6.71875,0 15,0 Z m 2.5,24.35547 V 20.64453 C 17.5,20.29297 17.22656,20 16.89453,20 h -3.75 C 12.79297,20 12.5,20.29297 12.5,20.64453 v 3.71094 C 12.5,24.70703 12.79297,25 13.14453,25 h 3.75 C 17.22656,25 17.5,24.70703 17.5,24.35547 Z M 17.4609,17.63672 17.81246,5.50781 c 0,-0.13672 -0.0586,-0.27343 -0.19531,-0.35156 C 17.49996,5.05855 17.32418,5 17.1484,5 h -4.29688 c -0.17578,0 -0.35156,0.0586 -0.46875,0.15625 -0.13672,0.0781 -0.19531,0.21484 -0.19531,0.35156 l 0.33203,12.12891 c 0,0.27344 0.29297,0.48828 0.66406,0.48828 h 3.61329 c 0.35156,0 0.64453,-0.21484 0.66406,-0.48828 z"
|
column: number
|
||||||
class="error" />
|
children: string[]
|
||||||
</g>
|
y: number
|
||||||
<g v-if="result === true" :transform="`translate(${x + boxWidth - 15}, ${y - 10})`">
|
parent?: GraphNode
|
||||||
<ellipse fill="#fff" cx="14.685823" cy="14.318233" rx="12.140151" ry="11.55523" />
|
}
|
||||||
<path d="m 25.078125,11.835938 c 0,-0.332032 -0.117188,-0.664063 -0.351563,-0.898438 L 22.949219,9.1796875 c -0.234375,-0.234375 -0.546875,-0.3710937 -0.878907,-0.3710937 -0.332031,0 -0.644531,0.1367187 -0.878906,0.3710937 L 13.222656,17.128906 8.8085938,12.714844 C 8.5742188,12.480469 8.2617188,12.34375 7.9296875,12.34375 c -0.3320313,0 -0.6445313,0.136719 -0.8789063,0.371094 l -1.7773437,1.757812 c -0.234375,0.234375 -0.3515625,0.566407 -0.3515625,0.898438 0,0.332031 0.1171875,0.644531 0.3515625,0.878906 l 7.0703125,7.070312 c 0.234375,0.234375 0.566406,0.371094 0.878906,0.371094 0.332032,0 0.664063,-0.136719 0.898438,-0.371094 L 24.726562,12.714844 c 0.234375,-0.234375 0.351563,-0.546875 0.351563,-0.878906 z M 30,15 C 30,23.28125 23.28125,30 15,30 6.71875,30 0,23.28125 0,15 0,6.71875 6.71875,0 15,0 23.28125,0 30,6.71875 30,15 Z"
|
|
||||||
class="check"/>
|
|
||||||
</g>
|
|
||||||
<g v-if="!node.children.length && (resultInclParents === null || resultInclParents === true)" :transform="`translate(${x + boxWidth + 25}, ${y + boxHeight/2 - 15})`">
|
|
||||||
<path d="m 25.078125,11.835938 c 0,-0.332032 -0.117188,-0.664063 -0.351563,-0.898438 L 22.949219,9.1796875 c -0.234375,-0.234375 -0.546875,-0.3710937 -0.878907,-0.3710937 -0.332031,0 -0.644531,0.1367187 -0.878906,0.3710937 L 13.222656,17.128906 8.8085938,12.714844 C 8.5742188,12.480469 8.2617188,12.34375 7.9296875,12.34375 c -0.3320313,0 -0.6445313,0.136719 -0.8789063,0.371094 l -1.7773437,1.757812 c -0.234375,0.234375 -0.3515625,0.566407 -0.3515625,0.898438 0,0.332031 0.1171875,0.644531 0.3515625,0.878906 l 7.0703125,7.070312 c 0.234375,0.234375 0.566406,0.371094 0.878906,0.371094 0.332032,0 0.664063,-0.136719 0.898438,-0.371094 L 24.726562,12.714844 c 0.234375,-0.234375 0.351563,-0.546875 0.351563,-0.878906 z M 30,15 C 30,23.28125 23.28125,30 15,30 6.71875,30 0,23.28125 0,15 0,6.71875 6.71875,0 15,0 23.28125,0 30,6.71875 30,15 Z"
|
|
||||||
class="check"/>
|
|
||||||
</g>
|
|
||||||
<g v-if="!node.children.length && (resultInclParents === false)" :transform="`translate(${x + boxWidth + 25}, ${y + boxHeight/2 - 15})`">
|
|
||||||
<path d="M 15,0 C 23.28125,0 30,6.71875 30,15 30,23.28125 23.28125,30 15,30 6.71875,30 0,23.28125 0,15 0,6.71875 6.71875,0 15,0 Z m 2.5,24.35547 V 20.64453 C 17.5,20.29297 17.22656,20 16.89453,20 h -3.75 C 12.79297,20 12.5,20.29297 12.5,20.64453 v 3.71094 C 12.5,24.70703 12.79297,25 13.14453,25 h 3.75 C 17.22656,25 17.5,24.70703 17.5,24.35547 Z M 17.4609,17.63672 17.81246,5.50781 c 0,-0.13672 -0.0586,-0.27343 -0.19531,-0.35156 C 17.49996,5.05855 17.32418,5 17.1484,5 h -4.29688 c -0.17578,0 -0.35156,0.0586 -0.46875,0.15625 -0.13672,0.0781 -0.19531,0.21484 -0.19531,0.35156 l 0.33203,12.12891 c 0,0.27344 0.29297,0.48828 0.66406,0.48828 h 3.61329 c 0.35156,0 0.64453,-0.21484 0.66406,-0.48828 z"
|
|
||||||
class="error" />
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
|
|
||||||
props: {
|
const props = defineProps<{
|
||||||
node: Object,
|
node: GraphNode
|
||||||
nodeid: String,
|
nodeid: string
|
||||||
children: Array,
|
children: GraphNode[]
|
||||||
boxWidth: Number,
|
boxWidth: number
|
||||||
boxHeight: Number,
|
boxHeight: number
|
||||||
marginX: Number,
|
marginX: number
|
||||||
marginY: Number,
|
marginY: number
|
||||||
paddingX: Number,
|
paddingX: number
|
||||||
},
|
}>()
|
||||||
computed: {
|
|
||||||
x() {
|
const x = computed(() => {
|
||||||
return this.node.column * (this.boxWidth + this.marginX) + this.marginX / 2 + this.paddingX
|
return props.node.column * (props.boxWidth + props.marginX) + props.marginX / 2 + props.paddingX
|
||||||
},
|
})
|
||||||
y() {
|
|
||||||
return this.node.y * (this.boxHeight + this.marginY) + this.marginY / 2
|
const y = computed(() => {
|
||||||
},
|
return props.node.y * (props.boxHeight + props.marginY) + props.marginY / 2
|
||||||
edges() {
|
})
|
||||||
const startX = this.x + this.boxWidth + 1
|
|
||||||
const startY = this.y + this.boxHeight / 2
|
const edges = computed(() => {
|
||||||
return this.children.map((c) => {
|
const startX = x.value + props.boxWidth + 1
|
||||||
const endX = (c.column * (this.boxWidth + this.marginX) + this.marginX / 2 + this.paddingX) - 1
|
const startY = y.value + props.boxHeight / 2
|
||||||
const endY = (c.y * (this.boxHeight + this.marginY) + this.marginY / 2) + this.boxHeight / 2
|
return props.children.map((c) => {
|
||||||
|
const endX = (c.column * (props.boxWidth + props.marginX) + props.marginX / 2 + props.paddingX) - 1
|
||||||
|
const endY = (c.y * (props.boxHeight + props.marginY) + props.marginY / 2) + props.boxHeight / 2
|
||||||
|
|
||||||
return `
|
return `
|
||||||
M ${startX} ${startY}
|
M ${startX} ${startY}
|
||||||
@@ -144,21 +47,23 @@
|
|||||||
C ${endX - 25} ${endY} ${endX - 25} ${endY} ${endX} ${endY}
|
C ${endX - 25} ${endY} ${endX - 25} ${endY} ${endX} ${endY}
|
||||||
`
|
`
|
||||||
})
|
})
|
||||||
},
|
})
|
||||||
checkEdge() {
|
|
||||||
const startX = this.x + this.boxWidth + 1
|
const checkEdge = computed(() => {
|
||||||
const startY = this.y + this.boxHeight / 2
|
const startX = x.value + props.boxWidth + 1
|
||||||
|
const startY = y.value + props.boxHeight / 2
|
||||||
|
|
||||||
return `M ${startX} ${startY} L ${startX + 25} ${startY}`
|
return `M ${startX} ${startY} L ${startX + 25} ${startY}`
|
||||||
},
|
})
|
||||||
rootEdge() {
|
|
||||||
if (this.node.column > 0) {
|
const rootEdge = computed(() => {
|
||||||
|
if (props.node.column > 0) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const startX = 0
|
const startX = 0
|
||||||
const startY = this.boxHeight / 2 + this.marginY / 2
|
const startY = props.boxHeight / 2 + props.marginY / 2
|
||||||
const endX = this.x - 1
|
const endX = x.value - 1
|
||||||
const endY = this.y + this.boxHeight / 2
|
const endY = y.value + props.boxHeight / 2
|
||||||
|
|
||||||
return `
|
return `
|
||||||
M ${startX} ${startY}
|
M ${startX} ${startY}
|
||||||
@@ -167,89 +72,171 @@
|
|||||||
L ${endX - 25} ${endY - 25 * Math.sign(endY - startY)}
|
L ${endX - 25} ${endY - 25 * Math.sign(endY - startY)}
|
||||||
C ${endX - 25} ${endY} ${endX - 25} ${endY} ${endX} ${endY}
|
C ${endX - 25} ${endY} ${endX - 25} ${endY} ${endX} ${endY}
|
||||||
`
|
`
|
||||||
},
|
})
|
||||||
variable () {
|
|
||||||
const op = this.operator;
|
|
||||||
if (this.node.rule[op] && this.node.rule[op][0]) {
|
|
||||||
if (this.node.rule[op][0]["entries_since"]) {
|
|
||||||
return "entries_since";
|
|
||||||
}
|
|
||||||
if (this.node.rule[op][0]["entries_before"]) {
|
|
||||||
return "entries_before";
|
|
||||||
}
|
|
||||||
if (this.node.rule[op][0]["entries_days_since"]) {
|
|
||||||
return "entries_days_since";
|
|
||||||
}
|
|
||||||
if (this.node.rule[op][0]["entries_days_before"]) {
|
|
||||||
return "entries_days_before";
|
|
||||||
}
|
|
||||||
return this.node.rule[op][0]["var"];
|
|
||||||
} else {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
},
|
|
||||||
vardata () {
|
|
||||||
return this.$root.VARS[this.variable];
|
|
||||||
},
|
|
||||||
varresult () {
|
|
||||||
const op = this.operator;
|
|
||||||
if (this.node.rule[op] && this.node.rule[op][0]) {
|
|
||||||
if (typeof this.node.rule[op][0]["__result"] === "undefined")
|
|
||||||
return null;
|
|
||||||
return this.node.rule[op][0]["__result"];
|
|
||||||
} else {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
},
|
|
||||||
rightoperand () {
|
|
||||||
const op = this.operator;
|
|
||||||
if (this.node.rule[op] && typeof this.node.rule[op][1] !== "undefined") {
|
|
||||||
return this.node.rule[op][1];
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
op: function () {
|
|
||||||
return this.$root.TYPEOPS[this.vardata.type][this.operator]
|
|
||||||
},
|
|
||||||
operands: function () {
|
|
||||||
return this.node.rule[this.operator]
|
|
||||||
},
|
|
||||||
operator: function () {
|
|
||||||
return Object.keys(this.node.rule).filter(function (k) { return !k.startsWith("__") })[0];
|
|
||||||
},
|
|
||||||
result: function () {
|
|
||||||
return typeof this.node.rule.__result == "undefined" ? null : !!this.node.rule.__result
|
|
||||||
},
|
|
||||||
resultInclParents: function () {
|
|
||||||
if (typeof this.node.rule.__result == "undefined")
|
|
||||||
return null
|
|
||||||
|
|
||||||
function _p(node) {
|
const operator = computed(() => {
|
||||||
|
return Object.keys(props.node.rule).filter((k) => !k.startsWith('__'))[0]
|
||||||
|
})
|
||||||
|
|
||||||
|
const variable = computed(() => {
|
||||||
|
const op = operator.value
|
||||||
|
if (props.node.rule[op] && props.node.rule[op][0]) {
|
||||||
|
if (props.node.rule[op][0]['entries_since']) {
|
||||||
|
return 'entries_since'
|
||||||
|
}
|
||||||
|
if (props.node.rule[op][0]['entries_before']) {
|
||||||
|
return 'entries_before'
|
||||||
|
}
|
||||||
|
if (props.node.rule[op][0]['entries_days_since']) {
|
||||||
|
return 'entries_days_since'
|
||||||
|
}
|
||||||
|
if (props.node.rule[op][0]['entries_days_before']) {
|
||||||
|
return 'entries_days_before'
|
||||||
|
}
|
||||||
|
return props.node.rule[op][0]['var']
|
||||||
|
} else {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const vardata = computed(() => {
|
||||||
|
return VARS[variable.value as keyof typeof VARS]
|
||||||
|
})
|
||||||
|
|
||||||
|
const varresult = computed(() => {
|
||||||
|
const op = operator.value
|
||||||
|
if (props.node.rule[op] && props.node.rule[op][0]) {
|
||||||
|
if (typeof props.node.rule[op][0]['__result'] === 'undefined')
|
||||||
|
return null
|
||||||
|
return props.node.rule[op][0]['__result']
|
||||||
|
} else {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const rightoperand = computed(() => {
|
||||||
|
const op = operator.value
|
||||||
|
if (props.node.rule[op] && typeof props.node.rule[op][1] !== 'undefined') {
|
||||||
|
return props.node.rule[op][1]
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const op = computed(() => {
|
||||||
|
return TYPEOPS[vardata.value.type as keyof typeof TYPEOPS]?.[operator.value as any]
|
||||||
|
})
|
||||||
|
|
||||||
|
const operands = computed(() => {
|
||||||
|
return props.node.rule[operator.value]
|
||||||
|
})
|
||||||
|
|
||||||
|
const result = computed(() => {
|
||||||
|
return typeof props.node.rule.__result === 'undefined' ? null : !!props.node.rule.__result
|
||||||
|
})
|
||||||
|
|
||||||
|
const resultInclParents = computed(() => {
|
||||||
|
if (typeof props.node.rule.__result === 'undefined') return null
|
||||||
|
|
||||||
|
function _p (node: GraphNode): boolean {
|
||||||
if (node.parent) {
|
if (node.parent) {
|
||||||
return node.rule.__result && _p(node.parent)
|
return node.rule.__result && _p(node.parent)
|
||||||
}
|
}
|
||||||
return node.rule.__result
|
return node.rule.__result
|
||||||
}
|
}
|
||||||
return _p(this.node)
|
return _p(props.node)
|
||||||
},
|
})
|
||||||
nodeClass: function () {
|
|
||||||
|
const nodeClass = computed(() => {
|
||||||
return {
|
return {
|
||||||
"node": true,
|
node: true,
|
||||||
"node-true": this.result === true,
|
'node-true': result.value === true,
|
||||||
"node-false": this.result === false,
|
'node-false': result.value === false,
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
},
|
|
||||||
methods: {
|
function df (val: string) {
|
||||||
df (val) {
|
const format = $('body').attr('data-datetimeformat')
|
||||||
const format = $("body").attr("data-datetimeformat")
|
|
||||||
return moment(val).format(format)
|
return moment(val).format(format)
|
||||||
},
|
|
||||||
tf (val) {
|
|
||||||
const format = $("body").attr("data-timeformat")
|
|
||||||
return moment(val, "HH:mm:ss").format(format)
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
function tf (val: string) {
|
||||||
|
const format = $('body').attr('data-timeformat')
|
||||||
|
return moment(val, 'HH:mm:ss').format(format)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
<template lang="pug">
|
||||||
|
g
|
||||||
|
path.edge(v-for="e in edges", :key="e", :d="e")
|
||||||
|
path.edge(v-if="rootEdge", :d="rootEdge")
|
||||||
|
path.edge(v-if="!node.children.length", :d="checkEdge")
|
||||||
|
rect(:width="boxWidth", :height="boxHeight", :x="x", :y="y", :class="nodeClass", rx="5")
|
||||||
|
|
||||||
|
foreignObject(:width="boxWidth - 10", :height="boxHeight - 10", :x="x + 5", :y="y + 5")
|
||||||
|
div.text(xmlns="http://www.w3.org/1999/xhtml")
|
||||||
|
span(v-if="vardata && vardata.type === 'int'")
|
||||||
|
span.fa.fa-sign-in(v-if="variable.startsWith('entries_')")
|
||||||
|
| {{ vardata.label }}
|
||||||
|
br
|
||||||
|
span(v-if="varresult !== null") {{ varresult }}
|
||||||
|
strong
|
||||||
|
| {{ op.label }} {{ rightoperand }}
|
||||||
|
span(v-else-if="vardata && vardata.type === 'int_by_datetime'")
|
||||||
|
span.fa.fa-sign-in(v-if="variable.startsWith('entries_')")
|
||||||
|
| {{ vardata.label }}
|
||||||
|
span(v-if="node.rule[operator][0][variable][0].buildTime[0] === 'custom'")
|
||||||
|
| {{ df(node.rule[operator][0][variable][0].buildTime[1]) }}
|
||||||
|
span(v-else-if="node.rule[operator][0][variable][0].buildTime[0] === 'customtime'")
|
||||||
|
| {{ tf(node.rule[operator][0][variable][0].buildTime[1]) }}
|
||||||
|
span(v-else)
|
||||||
|
| {{ TEXTS[node.rule[operator][0][variable][0].buildTime[0]] }}
|
||||||
|
br
|
||||||
|
span(v-if="varresult !== null") {{ varresult }}
|
||||||
|
strong
|
||||||
|
| {{ op.label }} {{ rightoperand }}
|
||||||
|
span(v-else-if="vardata && variable === 'now'")
|
||||||
|
span.fa.fa-clock-o
|
||||||
|
| {{ vardata.label }}
|
||||||
|
br
|
||||||
|
span(v-if="varresult !== null") {{ varresult }}
|
||||||
|
strong
|
||||||
|
| {{ op.label }}
|
||||||
|
br
|
||||||
|
span(v-if="rightoperand.buildTime[0] === 'custom'")
|
||||||
|
| {{ df(rightoperand.buildTime[1]) }}
|
||||||
|
span(v-else-if="rightoperand.buildTime[0] === 'customtime'")
|
||||||
|
| {{ tf(rightoperand.buildTime[1]) }}
|
||||||
|
span(v-else)
|
||||||
|
| {{ TEXTS[rightoperand.buildTime[0]] }}
|
||||||
|
span(v-if="operands[2]")
|
||||||
|
span(v-if="operator === 'isBefore'") +
|
||||||
|
span(v-else) -
|
||||||
|
| {{ operands[2] }}
|
||||||
|
| {{ TEXTS.minutes }}
|
||||||
|
span(v-else-if="vardata && operator === 'inList'")
|
||||||
|
span.fa.fa-sign-in(v-if="variable === 'gate'")
|
||||||
|
span.fa.fa-ticket(v-else)
|
||||||
|
| {{ vardata.label }}
|
||||||
|
span(v-if="varresult !== null") ({{ varresult }})
|
||||||
|
br
|
||||||
|
strong
|
||||||
|
| {{ rightoperand.objectList.map((o: any) => o.lookup[2]).join(", ") }}
|
||||||
|
span(v-else-if="vardata && vardata.type === 'enum_entry_status'")
|
||||||
|
span.fa.fa-check-circle-o
|
||||||
|
| {{ vardata.label }}
|
||||||
|
span(v-if="varresult !== null") ({{ varresult }})
|
||||||
|
br
|
||||||
|
strong
|
||||||
|
| {{ op.label }} {{ rightoperand }}
|
||||||
|
|
||||||
|
g(v-if="result === false", :transform="`translate(${x + boxWidth - 15}, ${y - 10})`")
|
||||||
|
ellipse(fill="#fff", cx="14.685823", cy="14.318233", rx="12.140151", ry="11.55523")
|
||||||
|
path.error(d="M 15,0 C 23.28125,0 30,6.71875 30,15 30,23.28125 23.28125,30 15,30 6.71875,30 0,23.28125 0,15 0,6.71875 6.71875,0 15,0 Z m 2.5,24.35547 V 20.64453 C 17.5,20.29297 17.22656,20 16.89453,20 h -3.75 C 12.79297,20 12.5,20.29297 12.5,20.64453 v 3.71094 C 12.5,24.70703 12.79297,25 13.14453,25 h 3.75 C 17.22656,25 17.5,24.70703 17.5,24.35547 Z M 17.4609,17.63672 17.81246,5.50781 c 0,-0.13672 -0.0586,-0.27343 -0.19531,-0.35156 C 17.49996,5.05855 17.32418,5 17.1484,5 h -4.29688 c -0.17578,0 -0.35156,0.0586 -0.46875,0.15625 -0.13672,0.0781 -0.19531,0.21484 -0.19531,0.35156 l 0.33203,12.12891 c 0,0.27344 0.29297,0.48828 0.66406,0.48828 h 3.61329 c 0.35156,0 0.64453,-0.21484 0.66406,-0.48828 z")
|
||||||
|
g(v-if="result === true", :transform="`translate(${x + boxWidth - 15}, ${y - 10})`")
|
||||||
|
ellipse(fill="#fff", cx="14.685823", cy="14.318233", rx="12.140151", ry="11.55523")
|
||||||
|
path.check(d="m 25.078125,11.835938 c 0,-0.332032 -0.117188,-0.664063 -0.351563,-0.898438 L 22.949219,9.1796875 c -0.234375,-0.234375 -0.546875,-0.3710937 -0.878907,-0.3710937 -0.332031,0 -0.644531,0.1367187 -0.878906,0.3710937 L 13.222656,17.128906 8.8085938,12.714844 C 8.5742188,12.480469 8.2617188,12.34375 7.9296875,12.34375 c -0.3320313,0 -0.6445313,0.136719 -0.8789063,0.371094 l -1.7773437,1.757812 c -0.234375,0.234375 -0.3515625,0.566407 -0.3515625,0.898438 0,0.332031 0.1171875,0.644531 0.3515625,0.878906 l 7.0703125,7.070312 c 0.234375,0.234375 0.566406,0.371094 0.878906,0.371094 0.332032,0 0.664063,-0.136719 0.898438,-0.371094 L 24.726562,12.714844 c 0.234375,-0.234375 0.351563,-0.546875 0.351563,-0.878906 z M 30,15 C 30,23.28125 23.28125,30 15,30 6.71875,30 0,23.28125 0,15 0,6.71875 6.71875,0 15,0 23.28125,0 30,6.71875 30,15 Z")
|
||||||
|
g(v-if="!node.children.length && (resultInclParents === null || resultInclParents === true)", :transform="`translate(${x + boxWidth + 25}, ${y + boxHeight/2 - 15})`")
|
||||||
|
path.check(d="m 25.078125,11.835938 c 0,-0.332032 -0.117188,-0.664063 -0.351563,-0.898438 L 22.949219,9.1796875 c -0.234375,-0.234375 -0.546875,-0.3710937 -0.878907,-0.3710937 -0.332031,0 -0.644531,0.1367187 -0.878906,0.3710937 L 13.222656,17.128906 8.8085938,12.714844 C 8.5742188,12.480469 8.2617188,12.34375 7.9296875,12.34375 c -0.3320313,0 -0.6445313,0.136719 -0.8789063,0.371094 l -1.7773437,1.757812 c -0.234375,0.234375 -0.3515625,0.566407 -0.3515625,0.898438 0,0.332031 0.1171875,0.644531 0.3515625,0.878906 l 7.0703125,7.070312 c 0.234375,0.234375 0.566406,0.371094 0.878906,0.371094 0.332032,0 0.664063,-0.136719 0.898438,-0.371094 L 24.726562,12.714844 c 0.234375,-0.234375 0.351563,-0.546875 0.351563,-0.878906 z M 30,15 C 30,23.28125 23.28125,30 15,30 6.71875,30 0,23.28125 0,15 0,6.71875 6.71875,0 15,0 23.28125,0 30,6.71875 30,15 Z")
|
||||||
|
g(v-if="!node.children.length && (resultInclParents === false)", :transform="`translate(${x + boxWidth + 25}, ${y + boxHeight/2 - 15})`")
|
||||||
|
path.error(d="M 15,0 C 23.28125,0 30,6.71875 30,15 30,23.28125 23.28125,30 15,30 6.71875,30 0,23.28125 0,15 0,6.71875 6.71875,0 15,0 Z m 2.5,24.35547 V 20.64453 C 17.5,20.29297 17.22656,20 16.89453,20 h -3.75 C 12.79297,20 12.5,20.29297 12.5,20.64453 v 3.71094 C 12.5,24.70703 12.79297,25 13.14453,25 h 3.75 C 17.22656,25 17.5,24.70703 17.5,24.35547 Z M 17.4609,17.63672 17.81246,5.50781 c 0,-0.13672 -0.0586,-0.27343 -0.19531,-0.35156 C 17.49996,5.05855 17.32418,5 17.1484,5 h -4.29688 c -0.17578,0 -0.35156,0.0586 -0.46875,0.15625 -0.13672,0.0781 -0.19531,0.21484 -0.19531,0.35156 l 0.33203,12.12891 c 0,0.27344 0.29297,0.48828 0.66406,0.48828 h 3.61329 c 0.35156,0 0.64453,-0.21484 0.66406,-0.48828 z")
|
||||||
|
</template>
|
||||||
|
|||||||
Reference in New Issue
Block a user