|
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