|
1 | 1 | <template>
|
2 | 2 | <div class="plot-data annotation">
|
3 | 3 | <div class="selectors">
|
4 |
| - <s-segmented-button v-model.lazy="props.annotation.axis"> |
5 |
| - <s-segmented-button-item value="y">{{ |
6 |
| - t("annotation.horizontal") |
7 |
| - }}</s-segmented-button-item> |
| 4 | + <s-segmented-button v-model.lazy="self.variable"> |
| 5 | + <s-segmented-button-item value="y"> |
| 6 | + {{ t("annotation.horizontal") }} |
| 7 | + </s-segmented-button-item> |
8 | 8 | <s-segmented-button-item value="x">
|
9 | 9 | {{ t("annotation.vertical") }}
|
10 | 10 | </s-segmented-button-item>
|
|
19 | 19 | >
|
20 | 20 | <SIconDelete />
|
21 | 21 | </s-icon-button>
|
22 |
| - {{ t("buttons.del") }} |
| 22 | + {{ t("annotation.topButton.delete") }} |
23 | 23 | </s-tooltip>
|
24 | 24 | <s-tooltip>
|
25 | 25 | <s-icon-button
|
|
29 | 29 | >
|
30 | 30 | <SIconTextfield />
|
31 | 31 | </s-icon-button>
|
32 |
| - {{ t("annotation.text") }} |
| 32 | + {{ t(`annotation.topButton.${showText ? "remove" : "add"}Text`) }} |
33 | 33 | </s-tooltip>
|
34 | 34 | <span class="annotation-drag drag-icon">
|
35 | 35 | <SIconDrag />
|
36 | 36 | </span>
|
37 | 37 | </div>
|
38 | 38 | </div>
|
39 | 39 |
|
40 |
| - <div class="annotation-texts" :class="{ showText }"> |
41 |
| - <s-text-field |
42 |
| - class="styled annotation-value" |
43 |
| - type="number" |
44 |
| - v-model="props.annotation.value" |
45 |
| - :label="props.annotation.axis + '='" |
46 |
| - ></s-text-field> |
| 40 | + <div class="annotation-fields" :class="{ showText }"> |
| 41 | + <div class="label-and-value"> |
| 42 | + <span class="label styled"> {{ self.variable + "=" }} </span> |
| 43 | + <s-text-field |
| 44 | + class="styled-inner value" |
| 45 | + type="number" |
| 46 | + v-model="self.value" |
| 47 | + :label="t('annotation.value')" |
| 48 | + @blur="handleValueBlur" |
| 49 | + ></s-text-field> |
| 50 | + </div> |
47 | 51 | <Transition name="anntextslide">
|
48 | 52 | <s-text-field
|
49 | 53 | v-if="showText"
|
50 |
| - class="annotation-textfield" |
| 54 | + class="text" |
51 | 55 | :label="t('annotation.text')"
|
52 |
| - v-model="props.annotation.text" |
| 56 | + v-model="self.text" |
53 | 57 | ></s-text-field>
|
54 | 58 | </Transition>
|
55 | 59 | </div>
|
56 | 60 | </div>
|
57 | 61 | </template>
|
58 | 62 |
|
59 | 63 | <script setup lang="ts">
|
60 |
| -import { InternalAnnotation } from "@/consts"; |
61 | 64 | import { useI18n } from "vue-i18n";
|
62 |
| -const { t } = useI18n(); |
| 65 | +import { I18nSchema } from "@/i18n"; |
| 66 | +const { t } = useI18n<{ message: I18nSchema }>(); |
| 67 | +
|
| 68 | +import SIconDelete from "@/ui/icons/delete.vue"; |
| 69 | +import SIconDrag from "@/ui/icons/drag.vue"; |
| 70 | +import SIconTextfield from "@/ui/icons/textfield.vue"; |
| 71 | +
|
| 72 | +import { useProfile } from "@/states"; |
| 73 | +import { PrivateAnnotation } from "@/types/annotation"; |
| 74 | +
|
| 75 | +import { ref, toRef, watch } from "vue"; |
63 | 76 |
|
| 77 | +const profile = useProfile(); |
64 | 78 | const props = defineProps<{
|
65 | 79 | index: number;
|
66 |
| - annotation: InternalAnnotation; |
| 80 | + self: PrivateAnnotation; |
67 | 81 | }>();
|
| 82 | +const self = toRef(props, "self"); |
| 83 | +
|
| 84 | +const showText = ref(self.value.text !== ""); |
| 85 | +watch(showText, (value) => { |
| 86 | + if (!value) self.value.text = ""; |
| 87 | +}); |
68 | 88 |
|
69 | 89 | import emitter from "@/mitt";
|
70 |
| -import { ref, watch } from "vue"; |
71 |
| -watch([() => props.annotation.axis, () => props.annotation.text], () => |
| 90 | +
|
| 91 | +watch([() => self.value.variable, () => self.value.text], () => |
72 | 92 | emitter.emit("require-full-update", "annotations axis change")
|
73 | 93 | );
|
74 | 94 |
|
75 |
| -import SIconDelete from "@/ui/icons/delete.vue"; |
76 |
| -import SIconDrag from "@/ui/icons/drag.vue"; |
77 |
| -import SIconTextfield from "@/ui/icons/textfield.vue"; |
| 95 | +function handleValueBlur() { |
| 96 | + self.value.value = Number(self.value.value); |
| 97 | +} |
78 | 98 |
|
79 |
| -import { useProfile } from "@/states"; |
80 |
| -const profile = useProfile(); |
| 99 | +import { Snackbar } from "sober"; |
81 | 100 | function deleteAnnotation() {
|
82 |
| - emitter.emit("require-full-update", "annotations axis change"); |
| 101 | + const backup = props.self; |
83 | 102 | profile.annotations.splice(props.index, 1);
|
| 103 | + emitter.emit("require-full-update", "annotations axis change"); |
| 104 | + profile.datum.splice(props.index, 1); |
| 105 | + Snackbar.builder({ |
| 106 | + text: t("editor.delete.success"), |
| 107 | + action: { |
| 108 | + text: t("editor.delete.undo"), |
| 109 | + click: () => { |
| 110 | + profile.annotations.splice(props.index, 0, backup); |
| 111 | + emitter.emit("require-full-update", "annotations axis change"); |
| 112 | + }, |
| 113 | + }, |
| 114 | + }); |
84 | 115 | }
|
85 |
| -
|
86 |
| -const showText = ref(props.annotation.text !== ""); |
87 |
| -
|
88 |
| -watch(showText, (value) => { |
89 |
| - if (!value) props.annotation.text = ""; |
90 |
| -}); |
91 | 116 | </script>
|
92 | 117 |
|
93 |
| -<style> |
| 118 | +<style lang="scss"> |
94 | 119 | .plot-data.annotation {
|
95 | 120 | display: flex;
|
96 | 121 | flex-direction: column;
|
97 | 122 | }
|
98 |
| -.annotation-value { |
99 |
| - font-size: 20px; |
100 |
| -} |
101 |
| -.annotation-textfield { |
102 |
| - font-size: 16px; |
103 |
| -} |
104 |
| -.annotation-texts { |
| 123 | +
|
| 124 | +.annotation-fields { |
105 | 125 | display: flex;
|
| 126 | + align-items: center; |
106 | 127 | gap: 10px;
|
107 | 128 | padding-top: 8px;
|
108 | 129 | overflow: hidden;
|
109 |
| -} |
110 |
| -.annotation-texts s-text-field { |
111 |
| - width: 0; |
112 |
| - flex-grow: 1; |
113 |
| -} |
114 |
| -</style> |
115 | 130 |
|
116 |
| -<style> |
117 |
| -.anntextslide-enter-from, |
118 |
| -.anntextslide-leave-to { |
119 |
| - flex-grow: 0 !important; |
120 |
| - margin-left: -10px; |
| 131 | + s-text-field { |
| 132 | + width: 0; |
| 133 | + flex-grow: 1; |
| 134 | + } |
| 135 | + .label-and-value { |
| 136 | + display: flex; |
| 137 | + align-items: center; |
| 138 | + flex-grow: 1; |
| 139 | + gap: 3px; |
| 140 | + .label { |
| 141 | + font-size: 25px; |
| 142 | + width: 1.9em; |
| 143 | + text-align: right; |
| 144 | + margin-bottom: -0.1em; |
| 145 | + } |
| 146 | + .value { |
| 147 | + font-size: 22px; |
| 148 | + } |
| 149 | + } |
| 150 | + .text { |
| 151 | + font-size: 16px; |
| 152 | + flex-grow: 2; |
| 153 | + } |
121 | 154 | }
|
122 | 155 |
|
123 |
| -.anntextslide-leave-active { |
124 |
| - transition: |
125 |
| - flex-grow var(--s-motion-duration-medium1) var(--s-motion-easing-emphasized), |
126 |
| - margin-left var(--s-motion-duration-medium1) |
127 |
| - var(--s-motion-easing-emphasized) 0.2s; |
128 |
| -} |
| 156 | +.anntextslide { |
| 157 | + &-enter-from, |
| 158 | + &-leave-to { |
| 159 | + flex-grow: 0 !important; |
| 160 | + margin-left: -10px; |
| 161 | + } |
| 162 | +
|
| 163 | + &-leave-active { |
| 164 | + transition: |
| 165 | + flex-grow var(--s-motion-duration-medium1) |
| 166 | + var(--s-motion-easing-emphasized), |
| 167 | + margin-left var(--s-motion-duration-medium1) |
| 168 | + var(--s-motion-easing-emphasized) 0.2s; |
| 169 | + } |
129 | 170 |
|
130 |
| -.anntextslide-enter-active { |
131 |
| - transition: |
132 |
| - flex-grow var(--s-motion-duration-medium1) var(--s-motion-easing-emphasized), |
133 |
| - margin-left var(--s-motion-duration-medium1) |
134 |
| - var(--s-motion-easing-emphasized); |
| 171 | + &-enter-active { |
| 172 | + transition: |
| 173 | + flex-grow var(--s-motion-duration-medium1) |
| 174 | + var(--s-motion-easing-emphasized), |
| 175 | + margin-left var(--s-motion-duration-medium1) |
| 176 | + var(--s-motion-easing-emphasized); |
| 177 | + } |
135 | 178 | }
|
136 | 179 | </style>
|
0 commit comments