Line data Source code
1 : // @ts-check
2 :
3 : import Vue from "vue";
4 : import { mapState } from "vuex";
5 : import { get, isNil, noop } from "lodash";
6 : import { select } from "d3-selection";
7 : import { isCSS } from "@cern/ssvg-engine";
8 : import RelationCodeEditor from "./RelationCodeEditor.vue";
9 : import Card from "../Card.vue";
10 : import { getAttribute } from "../../utils/element";
11 : import d from "debug";
12 : import RelationEditValue from "./RelationEditValue.vue";
13 : import { BaseKeyboardEventMixin as KBMixin } from "@cern/base-vue";
14 :
15 1 : const debug = d("app:edit");
16 :
17 : /**
18 : * @typedef {{ card: V.Instance<Card>, cm: V.Instance<RelationCodeEditor> }} Refs
19 : */
20 :
21 : export default /** @type {V.Constructor<any, Refs>} */(Vue).extend({
22 : name: "RelationEdit",
23 : components: { Card, RelationCodeEditor, RelationEditValue },
24 : mixins: [ KBMixin({ local: true }) ],
25 : props: { title: { type: String, default: "Relation" } },
26 : /**
27 : * @return {{
28 : * selection: any,
29 : * directSelection: any,
30 : * value: any,
31 : * timer: NodeJS.Timeout|null,
32 : * attributeName: string,
33 : * querySelector: string,
34 : * attributeType: string,
35 : * initialValue: any,
36 : * }}
37 : */
38 : data() {
39 3 : return {
40 : selection: null,
41 : directSelection: null,
42 : value: null,
43 : timer: null,
44 : attributeName: "",
45 : querySelector: "",
46 : attributeType: "auto",
47 : initialValue: undefined
48 : };
49 : },
50 : computed: {
51 : .../**
52 : @type {{ normalizedValue(): number, relation(): Element|null }}
53 : */(mapState("selection", [ "relation", "normalizedValue" ])),
54 : .../**
55 : @type {{ directSvg(): any, svg(): any, state(): any, svgSelectors(): any }}
56 : */(mapState("engine", {
57 5 : directSvg: (state) => get(state, [ "directEngine", "svg" ]),
58 5 : svg: (state) => get(state, [ "engine", "svg" ]),
59 7 : state: (state) => get(state, [ "state" ]),
60 0 : svgSelectors: (state) => get(state, [ "svgSelectors" ])
61 : })),
62 : .../** @type {{ showKeyHints(): boolean }} */(mapState("ui", [ "showKeyHints" ]))
63 : },
64 : watch: {
65 : relation: {
66 : immediate: true,
67 5 : handler() { this.restore(); this.load(); }
68 : },
69 2 : svg() { this.updateSelection(); },
70 : state: {
71 : immediate: true,
72 : deep: true,
73 : handler() {
74 7 : if (this.timer) {
75 4 : clearTimeout(this.timer);
76 : }
77 7 : this.timer = setTimeout(this.onTimer, 150);
78 : }
79 : }
80 : },
81 : mounted() {
82 3 : this.onKey("ctrl-s-keydown", (/** @type {Event} */e) => {
83 0 : if (this.$refs.card?.isFocused) {
84 0 : e.preventDefault(); this.doApply();
85 : }
86 : });
87 : },
88 : beforeDestroy() {
89 3 : if (this.timer) {
90 3 : clearTimeout(this.timer);
91 : }
92 3 : this.restore();
93 : },
94 : methods: {
95 : load() {
96 5 : this.attributeName = getAttribute(this.relation, "attribute-name");
97 5 : this.querySelector = getAttribute(this.relation, "query-selector");
98 5 : this.attributeType = getAttribute(this.relation, "attribute-type") || "auto";
99 5 : this.updateSelection();
100 : },
101 : updateSelection() {
102 7 : this.selection = select(this.svg).selectAll(this.querySelector);
103 7 : this.directSelection = select(this.directSvg).selectAll(this.querySelector);
104 : },
105 : onTimer() {
106 0 : if (!this.$refs.card?.isFocused) {
107 0 : const attributeName = this.getAttributeName();
108 0 : if (!this.directSelection?.node()) {
109 0 : this.value = null;
110 : }
111 0 : else if (isNil(attributeName)) {
112 0 : this.value = this.directSelection.text();
113 : }
114 0 : else if ("CSS" === this.getAttributeType()) {
115 0 : this.value = this.directSelection.style(attributeName);
116 : }
117 : else {
118 0 : this.value = this.directSelection.attr(attributeName);
119 : }
120 : }
121 0 : this.timer = setTimeout(this.onTimer, 500);
122 : },
123 : /**
124 : * @param {string} value
125 : */ /* eslint-disable-next-line complexity */
126 : setValue(value) {
127 0 : debug("setValue:%s", value);
128 0 : if (!this.selection || !this.directSelection) { return; }
129 :
130 0 : this.value = value;
131 0 : const attributeName = this.getAttributeName();
132 0 : if (isNil(attributeName)) {
133 0 : if (this.initialValue === undefined && this.selection?.node()) {
134 0 : this.initialValue = this.selection.text();
135 : }
136 0 : this.selection.text(value);
137 0 : this.directSelection.text(value);
138 : }
139 0 : else if ("CSS" === this.getAttributeType()) {
140 0 : if (this.initialValue === undefined && this.selection?.node()) {
141 0 : this.initialValue = this.selection.style(attributeName);
142 : }
143 0 : this.selection.style(attributeName, value);
144 0 : this.directSelection.style(attributeName, value);
145 : }
146 : else {
147 0 : if (this.initialValue === undefined && this.selection?.node()) {
148 0 : this.initialValue = this.selection.attr(attributeName);
149 : }
150 0 : this.selection.attr(attributeName, value);
151 0 : this.directSelection.attr(attributeName, value);
152 : }
153 0 : debug("set initalValue:%s", this.initialValue);
154 : },
155 : restore() {
156 8 : debug("restore value:%s", this.initialValue);
157 8 : if (!this.selection || !this.directSelection) { return; }
158 5 : else if (this.initialValue === undefined) { return; }
159 :
160 0 : const attributeName = this.getAttributeName();
161 0 : if (isNil(attributeName)) {
162 0 : this.selection.text(this.initialValue);
163 0 : this.directSelection.text(this.initialValue);
164 : }
165 0 : else if ("CSS" === this.getAttributeType()) {
166 0 : this.selection.style(attributeName, this.initialValue);
167 0 : this.directSelection.style(attributeName, this.initialValue);
168 : }
169 : else {
170 0 : this.selection.attr(attributeName, this.initialValue);
171 0 : this.directSelection.attr(attributeName, this.initialValue);
172 : }
173 0 : this.value = this.initialValue;
174 0 : this.initialValue = undefined;
175 : },
176 : /**
177 : * @param {string} name
178 : * @param {string} value
179 : */
180 : setParam(name, value) {
181 0 : debug("setParam %s=%s", name, value);
182 0 : this.restore();
183 0 : Vue.set(this, name, value);
184 0 : this.updateSelection();
185 0 : this.setValue(this.value);
186 : },
187 : /**
188 : * @return {string}
189 : */
190 : getAttributeType() {
191 0 : if (!this.attributeType || this.attributeType === "auto") {
192 0 : return isCSS(this.getAttributeName()) ? "CSS" : "XML";
193 : }
194 : else {
195 0 : return this.attributeType;
196 : }
197 : },
198 : /**
199 : * @return {string}
200 : */
201 : getAttributeName() {
202 0 : return this.attributeName;
203 : },
204 : async doApply() {
205 0 : if (!this.relation) { return; }
206 0 : this.$refs.cm?.doApply();
207 : },
208 : doDelete() {
209 0 : if (!this.relation) { return; }
210 0 : this.$store.dispatch("remove", "relation").catch(noop);
211 : }
212 : }
213 : });
|