Skip to content

Commit 01b9b9f

Browse files
authored
Merge pull request #14 from Linho1219/function-field-enhance
函数框语法高亮
2 parents ad8c5de + b4346c8 commit 01b9b9f

File tree

11 files changed

+1085
-20
lines changed

11 files changed

+1085
-20
lines changed

package-lock.json

Lines changed: 692 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "function-plot-gui",
33
"private": true,
4-
"version": "0.5",
4+
"version": "0.6",
55
"type": "module",
66
"scripts": {
77
"dev": "vite",
@@ -32,6 +32,7 @@
3232
"@types/node": "^22.14.1",
3333
"@types/utf8": "^3.0.3",
3434
"@vitejs/plugin-vue": "^5.2.1",
35+
"@vitejs/plugin-vue-jsx": "^4.1.2",
3536
"dotenv": "^16.5.0",
3637
"patch-package": "^8.0.0",
3738
"rollup-plugin-visualizer": "^5.14.0",

public/fonts/KaTeX_AllInOne.woff2

-24 Bytes
Binary file not shown.

src/editor/inputs/implicit.vue

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
<template>
22
<div class="input-inner">
33
<div class="field main-fn">
4-
<FunctionField class="fn" label="f(x, y)" v-model="self.fn" />
4+
<FunctionField
5+
class="fn"
6+
label="f(x,y)"
7+
v-model="self.fn"
8+
:identifiers="['x', 'y']"
9+
/>
510
<span class="label styled"> =0 </span>
611
</div>
712
<AnimatedFold :folded="props.folded">
@@ -17,10 +22,7 @@
1722
</div>
1823
<div class="switches">
1924
<!-- closed -->
20-
<s-checkbox
21-
type="checkbox"
22-
v-model.lazy="self.closed"
23-
>
25+
<s-checkbox type="checkbox" v-model.lazy="self.closed">
2426
{{ t("data.more.closed") }}
2527
</s-checkbox>
2628
</div>

src/editor/inputs/parametric.vue

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,21 @@
22
<div class="input-inner">
33
<div class="field main-fn">
44
<span class="label styled">x=</span>
5-
<FunctionField class="fn" label="f(t)" v-model="self.x" />
5+
<FunctionField
6+
class="fn"
7+
label="f(t)"
8+
v-model="self.x"
9+
:identifiers="['t']"
10+
/>
611
</div>
712
<div class="field main-fn">
813
<span class="label styled">y=</span>
9-
<FunctionField class="fn" label="g(t)" v-model="self.y" />
14+
<FunctionField
15+
class="fn"
16+
label="g(t)"
17+
v-model="self.y"
18+
:identifiers="['t']"
19+
/>
1020
</div>
1121
<AnimatedFold :folded="props.folded">
1222
<s-divider>{{ t("data.more.dividerTitle") }}</s-divider>

src/editor/inputs/polar.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
ref="inputBox"
88
label="f(θ)"
99
v-model="self.r"
10+
:identifiers="['theta']"
1011
/>
1112
</div>
1213
<AnimatedFold :folded="props.folded">
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
[
2+
"''''",
3+
"'''",
4+
"''",
5+
"QED",
6+
"Q.E.D.",
7+
"s.t.",
8+
"PI",
9+
"theta",
10+
"abs",
11+
"grad",
12+
"Inf",
13+
"inf",
14+
"lim",
15+
"max",
16+
"min",
17+
"mod",
18+
"NaN",
19+
"sqrt",
20+
"rank",
21+
"sgn",
22+
"sup",
23+
"exp",
24+
"ln",
25+
"log",
26+
"acos",
27+
"arccos",
28+
"arcsin",
29+
"arctan",
30+
"asin",
31+
"atan",
32+
"cos",
33+
"cosh",
34+
"cot",
35+
"csc",
36+
"sec",
37+
"sin",
38+
"sinh",
39+
"tan",
40+
"tanh"
41+
]

src/editor/inputs/subblocks/function.vue

Lines changed: 84 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
<template>
22
<div class="filled-textfield" :class="{ focus: isFocus }">
3-
<label :class="{ lifted: !isEmpty }">{{ props.label }}</label>
3+
<label :class="{ lifted: !isEmpty }" class="function-label">
4+
<component v-for="item in labelContent" :is="item"></component>
5+
</label>
46
<input
57
@focus="isFocus = true"
68
@blur="isFocus = false"
@@ -11,12 +13,14 @@
1113
</div>
1214
</template>
1315

14-
<script setup lang="ts">
16+
<script setup lang="tsx">
17+
/* @jsxImportSource vue */
1518
import { computed, ref, watch } from "vue";
1619
1720
const value = defineModel<string>({ required: true });
1821
const props = defineProps<{
1922
label: string;
23+
identifiers?: string[];
2024
}>();
2125
const isFocus = ref(false);
2226
const isEmpty = computed(() => value.value === "");
@@ -32,6 +36,26 @@ function refreshInput() {
3236
inputRef.value.setSelectionRange(selectionStart, selectionEnd);
3337
}
3438
watch(value, refreshInput);
39+
40+
import { tokenize } from "./functionTokenize";
41+
const labelContent = computed(() =>
42+
isEmpty.value
43+
? [<span>{props.label}</span>]
44+
: tokenize(value.value, props.identifiers || ["x"]).map((token) => (
45+
<span
46+
class={[
47+
token.type,
48+
token.raw,
49+
"function-label-item",
50+
token.level ? "level-" + Math.min(token.level, 5) : undefined,
51+
token.err ? "error" : undefined,
52+
token.sqrtLevel ? "under-sqrt" : undefined,
53+
]}
54+
>
55+
<span class="inner">{token.raw}</span>
56+
</span>
57+
))
58+
);
3559
</script>
3660

3761
<style lang="scss">
@@ -46,6 +70,7 @@ watch(value, refreshInput);
4670
padding: 0;
4771
display: flex;
4872
font-family: var(--font-math);
73+
position: relative;
4974
&.focus {
5075
background-color: var(--s-color-surface-container-highest);
5176
border-bottom-color: var(--s-color-primary);
@@ -60,24 +85,76 @@ watch(value, refreshInput);
6085
width: 0;
6186
flex-grow: 1;
6287
caret-color: var(--s-color-primary);
63-
line-height: 1.2;
6488
z-index: 1;
65-
color: var(--s-color);
89+
color: transparent;
6690
}
6791
label {
92+
display: block;
6893
color: var(--s-color-outline);
6994
position: absolute;
70-
line-height: 1.2;
95+
white-space: pre;
96+
max-width: 100%;
97+
box-sizing: border-box;
7198
}
7299
input,
73100
label {
74-
padding: 0.4em 0.45em 0.3em 0.45em;
101+
padding: 0.2em 0.45em 0.1em 0.45em;
102+
line-height: 1.6;
103+
overflow: hidden;
75104
}
76105
&.styled label {
77106
transform: translateY(-0.05em);
78107
}
79108
label.lifted {
80-
opacity: 0;
109+
color: var(--s-color);
110+
}
111+
}
112+
113+
.function-label {
114+
.function-label-item,
115+
.function-label-item .inner {
116+
display: inline-block;
117+
}
118+
.identifier {
119+
color: var(--s-color-primary);
120+
}
121+
.operator {
122+
color: var(--s-color-secondary);
123+
}
124+
.bracket {
125+
color: var(--s-color-secondary);
126+
}
127+
@for $i from 1 through 5 {
128+
.bracket.level-#{$i} .inner {
129+
transform: scaleY(#{0.8 + $i * 0.2});
130+
}
131+
}
132+
.unknown {
133+
color: var(--s-color-error);
134+
}
135+
.function {
136+
color: var(--s-color-tertiary);
137+
}
138+
.error .inner {
139+
text-decoration: underline var(--s-color-error);
140+
text-decoration-thickness: 0.05em;
141+
text-underline-offset: 0.1em;
142+
}
143+
.sqrt {
144+
transform: scaleY(1.3) translateY(-1px);
145+
}
146+
.under-sqrt {
147+
background-image: linear-gradient(
148+
to bottom,
149+
transparent 0.1em,
150+
var(--s-color-tertiary) 0.1em,
151+
var(--s-color-tertiary) 0.14em,
152+
transparent 0.14em,
153+
transparent 100%
154+
);
155+
&.sqrt {
156+
transform: none !important;
157+
}
81158
}
82159
}
83160
</style>

0 commit comments

Comments
 (0)