Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
82e6ca8
调整目录结构
Linho1219 Apr 30, 2025
eaeb623
新组件结构
Linho1219 Apr 30, 2025
cf7aa33
组件代码初始化
Linho1219 Apr 30, 2025
bd9afbd
重写类型定义
Linho1219 Apr 30, 2025
2e7d8dc
完善注释与工具函数
Linho1219 May 1, 2025
08bfd29
重构状态定义
Linho1219 May 1, 2025
5958dcd
绘图组件跟随新定义
Linho1219 May 1, 2025
5bc0719
key 从 symbol 改回 number
Linho1219 May 1, 2025
2777624
修复导入对象多余属性
Linho1219 May 1, 2025
963adbe
图像渲染死循环问题修复
Linho1219 May 1, 2025
885f443
输出优化
Linho1219 May 1, 2025
92dd15e
标线编辑器适配新数据结构
Linho1219 May 1, 2025
2442225
打补丁设置组件库字体
Linho1219 May 1, 2025
fa54d2a
更新依赖
Linho1219 May 1, 2025
8ff7f23
修复删除操作异常
Linho1219 May 1, 2025
56b8ffc
调整标线 UI
Linho1219 May 1, 2025
25ee8e9
优化字体
Linho1219 May 1, 2025
f5a8721
调整 esbuild 配置
Linho1219 May 1, 2025
9428bef
开始重构函数列表
Linho1219 May 1, 2025
a7e7013
补充输出过滤属性
Linho1219 May 1, 2025
f77e763
恢复 ripple 模糊效果
Linho1219 May 1, 2025
3338562
移除对 .min.js 的补丁
Linho1219 May 1, 2025
2569315
导入组件跟随新类型定义
Linho1219 May 1, 2025
e46e9aa
更新 sober
Linho1219 May 4, 2025
2976fc7
图像类型切换处理
Linho1219 May 4, 2025
ef766dd
样式
Linho1219 May 6, 2025
eeec1c8
填充文本框组件
Linho1219 May 6, 2025
d039c5d
样式
Linho1219 May 6, 2025
c674ae5
线性方程新 UI
Linho1219 May 6, 2025
5fb638c
通用组件重构,颜色选择器
Linho1219 May 7, 2025
274a631
颜色选择器完整实现
Linho1219 May 7, 2025
37e7c74
定义域设置
Linho1219 May 7, 2025
97492c6
优化颜色选择器对 HEX 的支持
Linho1219 May 7, 2025
1bbfe5f
颜色选择器添加清除按钮
Linho1219 May 7, 2025
6eab494
重构 i18n 结构
Linho1219 May 7, 2025
67e3837
标线删除撤销
Linho1219 May 7, 2025
7ed31a0
修复坐标轴选项导出问题
Linho1219 May 7, 2025
add572e
切线割线面板
Linho1219 May 7, 2025
909390b
切割线面板 UI
Linho1219 May 8, 2025
f7e1884
切割线数据并入结构
Linho1219 May 8, 2025
d67de16
切割线面板 i18n
Linho1219 May 8, 2025
b3a8705
优化切割线面板指示器逻辑
Linho1219 May 8, 2025
685f479
修复导入 bug
Linho1219 May 8, 2025
826bf06
使用自定义组件替代 s-fold
Linho1219 May 8, 2025
b79b2da
类型定义修复
Linho1219 May 8, 2025
f4ca35e
隐函数面板
Linho1219 May 8, 2025
044afd7
参数方程面板
Linho1219 May 8, 2025
2807d7c
极坐标方程面板
Linho1219 May 8, 2025
7459c01
点集面板
Linho1219 May 8, 2025
76ec681
向量面板
Linho1219 May 8, 2025
ce7505b
文本面板
Linho1219 May 8, 2025
d2af30b
添加连字问题 workaround
Linho1219 May 8, 2025
0a7f709
导数面板交互优化
Linho1219 May 8, 2025
5e85bf2
字体优化
Linho1219 May 8, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 2 additions & 20 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,32 +13,14 @@
"base": "lottie",
"color": "blue-800",
"lightColor": "light-blue-200",
"folderNames": ["graph"],
"rootFolderNames": ["graph"]
"folderNames": ["graph"]
},
{
"name": "input-component",
"base": "mock",
"color": "cyan-800",
"lightColor": "light-green-700",
"folderNames": ["inputs"],
"rootFolderNames": ["inputs"]
},
{
"name": "icon",
"base": "svg",
"color": "purple-400",
"lightColor": "purple-100",
"folderNames": ["icons"],
"rootFolderNames": ["icons"]
},
{
"name": "animate",
"base": "animation",
"color": "purple-400",
"lightColor": "purple-100",
"folderNames": ["animatedList"],
"rootFolderNames": ["animatedList"]
"folderNames": ["inputs", "subblocks"]
}
],
"material-icon-theme.files.associations": {
Expand Down
1,023 changes: 755 additions & 268 deletions package-lock.json

Large diffs are not rendered by default.

8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "function-plot-gui",
"private": true,
"version": "0.4.0",
"version": "0.5",
"type": "module",
"scripts": {
"dev": "vite",
Expand All @@ -19,9 +19,10 @@
"mitt": "^3.0.1",
"pinia": "^3.0.2",
"prettier": "^3.4.2",
"sober": "^1.1.0",
"sober": "^1.1.1",
"utf8": "^3.0.0",
"vue": "^3.5.13",
"vue-color-kit": "^1.0.6",
"vue-draggable-plus": "^0.6.0",
"vue-i18n": "^10.0.7"
},
Expand All @@ -34,8 +35,9 @@
"dotenv": "^16.5.0",
"patch-package": "^8.0.0",
"rollup-plugin-visualizer": "^5.14.0",
"sass-embedded": "^1.87.0",
"typescript": "~5.6.2",
"vite": "^6.1.0",
"vite": "^6.3.5",
"vue-tsc": "^2.1.10"
}
}
184 changes: 0 additions & 184 deletions patches/sober+1.1.0.patch

This file was deleted.

8 changes: 8 additions & 0 deletions patches/sober+1.1.1.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
diff --git a/node_modules/sober/dist/popup.js b/node_modules/sober/dist/popup.js
index 1bbbdb4..f2e8485 100644
--- a/node_modules/sober/dist/popup.js
+++ b/node_modules/sober/dist/popup.js
@@ -1,2 +1,2 @@
-import{useElement as w}from"./core/element.js";import{convertCSSDuration as y}from"./core/utils/CSSUtils.js";const E="s-popup",k={align:"bottom"},S=":host{display:inline-block;vertical-align:middle;text-align:left}dialog{inset:0;width:100%;height:100%;background:none;border:none;padding:0;max-width:none;max-height:none;outline:none;position:relative;overflow:hidden;color:inherit}dialog::backdrop{background:none}.scrim{position:absolute;top:0;left:0;width:100%;height:100%}.container{position:relative;width:fit-content;max-width:100%;max-height:100%}::slotted(:not([slot])){border-radius:4px;max-width:100%;max-height:100%;white-space:nowrap;box-shadow:var(--s-elevation-level2, 0 2px 4px -1px rgba(0, 0, 0, .2), 0 4px 5px 0 rgba(0, 0, 0, .14), 0 1px 10px 0 rgba(0, 0, 0, .12));background:var(--s-color-surface-container, #ECEEF0)}",C='<slot name="trigger"></slot><dialog class="popup" part="popup"><div class="scrim" part="scrim"></div><div class="container" part="container"><slot></slot></div></dialog>';class b extends w({style:S,template:C,props:k,setup(l){const r=l.querySelector("dialog"),n=l.querySelector(".container"),p=getComputedStyle(this),h=()=>{const e=p.getPropertyValue("--s-motion-easing-standard")||"cubic-bezier(0.2, 0, 0, 1.0)",t=p.getPropertyValue("--s-motion-duration-medium4")||"400ms";return{easing:e,duration:y(t)}},d=e=>{if(!this.isConnected||r.open)return;const t={top:0,left:0,origin:[]};if(r.showModal(),!this.dispatchEvent(new Event("show",{cancelable:!0})))return r.close();const o=n.offsetWidth,c=n.offsetHeight;if(!e||e instanceof HTMLElement){const g=e??this;if(!g)return;const i=g.getBoundingClientRect(),a={middle(f){t.origin[0]="center",t.left=i.left-(o-i.width)/2;const m=()=>(t.top=i.top+i.height,t.origin[1]="top",t.top+c>innerHeight),u=()=>(t.top=i.top-c,t.origin[1]="bottom",t.top<0);t.left<0&&(t.left=i.left,t.origin[0]="left"),t.left+o>innerWidth&&(t.left=i.left+i.width-o,t.origin[0]="right"),f==="top"&&u()&&m(),f==="bottom"&&m()&&u()},left(){return t.origin=["right","top"],t.left=i.left-o,t.top=i.top,t.left<0},right(){return t.origin=["left","top"],t.left=i.left+i.width,t.top=i.top,t.left+o>innerWidth}};switch(this.align){case"bottom":case"top":a.middle(this.align);break;case"left":a.left()&&a.right();break;case"right":a.right()&&a.left();break}}else t.top=e.y,t.left=e.x,t.origin=e.origin?.split(" ")??["left","top"],e.x+o>innerWidth&&(t.left=e.x-o,t.origin[0]="right"),e.y+c>innerHeight&&(t.top=e.y-c,t.origin[1]="bottom");n.style.transformOrigin=t.origin.join(" "),n.style.top=`${Math.max(t.top,0)}px`,n.style.left=`${Math.max(t.left,0)}px`;const x=n.animate({transform:["scale(.9)","scale(1)"],opacity:[0,1]},h());this.setAttribute("showed",""),x.finished.then(()=>this.dispatchEvent(new Event("showed")))},s=()=>{if(!this.isConnected||!r.open||n.getAnimations().length>0||!this.dispatchEvent(new Event("close",{cancelable:!0})))return;const e=n.animate({transform:["scale(1)","scale(.9)"],opacity:[1,0]},h());this.removeAttribute("showed"),e.finished.then(()=>{r.close(),this.dispatchEvent(new Event("closed"))})},v=e=>r.open?s():d(e);return l.querySelector("slot[name=trigger]").addEventListener("click",()=>d()),l.querySelector(".scrim").addEventListener("pointerdown",s),{expose:{show:d,toggle:v,close:s},onMounted:()=>addEventListener("resize",s),onUnmounted:()=>removeEventListener("resize",s)}}}){}b.define(E);export{b as Popup};
+import{useElement as w}from"./core/element.js";import{convertCSSDuration as y}from"./core/utils/CSSUtils.js";const E="s-popup",k={align:"bottom"},S=":host{display:inline-block;vertical-align:middle;text-align:left}dialog{inset:0;width:100%;height:100%;background:none;border:none;padding:0;max-width:none;max-height:none;outline:none;position:relative;overflow:hidden;color:inherit}dialog::backdrop{background:none}.scrim{cursor:default;position:absolute;top:0;left:0;width:100%;height:100%}.container{position:relative;width:fit-content;max-width:100%;max-height:100%}::slotted(:not([slot])){border-radius:4px;max-width:100%;max-height:100%;white-space:nowrap;box-shadow:var(--s-elevation-level2, 0 2px 4px -1px rgba(0, 0, 0, .2), 0 4px 5px 0 rgba(0, 0, 0, .14), 0 1px 10px 0 rgba(0, 0, 0, .12));background:var(--s-color-surface-container, #ECEEF0)}",C='<slot name="trigger"></slot><dialog class="popup" part="popup"><div class="scrim" part="scrim"></div><div class="container" part="container"><slot></slot></div></dialog>';class b extends w({style:S,template:C,props:k,setup(l){const r=l.querySelector("dialog"),n=l.querySelector(".container"),p=getComputedStyle(this),h=()=>{const e=p.getPropertyValue("--s-motion-easing-standard")||"cubic-bezier(0.2, 0, 0, 1.0)",t=p.getPropertyValue("--s-motion-duration-medium4")||"400ms";return{easing:e,duration:y(t)}},d=e=>{if(!this.isConnected||r.open)return;const t={top:0,left:0,origin:[]};if(r.showModal(),!this.dispatchEvent(new Event("show",{cancelable:!0})))return r.close();const o=n.offsetWidth,c=n.offsetHeight;if(!e||e instanceof HTMLElement){const g=e??this;if(!g)return;const i=g.getBoundingClientRect(),a={middle(f){t.origin[0]="center",t.left=i.left-(o-i.width)/2;const m=()=>(t.top=i.top+i.height,t.origin[1]="top",t.top+c>innerHeight),u=()=>(t.top=i.top-c,t.origin[1]="bottom",t.top<0);t.left<0&&(t.left=i.left,t.origin[0]="left"),t.left+o>innerWidth&&(t.left=i.left+i.width-o,t.origin[0]="right"),f==="top"&&u()&&m(),f==="bottom"&&m()&&u()},left(){return t.origin=["right","top"],t.left=i.left-o,t.top=i.top,t.left<0},right(){return t.origin=["left","top"],t.left=i.left+i.width,t.top=i.top,t.left+o>innerWidth}};switch(this.align){case"bottom":case"top":a.middle(this.align);break;case"left":a.left()&&a.right();break;case"right":a.right()&&a.left();break}}else t.top=e.y,t.left=e.x,t.origin=e.origin?.split(" ")??["left","top"],e.x+o>innerWidth&&(t.left=e.x-o,t.origin[0]="right"),e.y+c>innerHeight&&(t.top=e.y-c,t.origin[1]="bottom");n.style.transformOrigin=t.origin.join(" "),n.style.top=`${Math.max(t.top,0)}px`,n.style.left=`${Math.max(t.left,0)}px`;const x=n.animate({transform:["scale(.9)","scale(1)"],opacity:[0,1]},h());this.setAttribute("showed",""),x.finished.then(()=>this.dispatchEvent(new Event("showed")))},s=()=>{if(!this.isConnected||!r.open||n.getAnimations().length>0||!this.dispatchEvent(new Event("close",{cancelable:!0})))return;const e=n.animate({transform:["scale(1)","scale(.9)"],opacity:[1,0]},h());this.removeAttribute("showed"),e.finished.then(()=>{r.close(),this.dispatchEvent(new Event("closed"))})},v=e=>r.open?s():d(e);return l.querySelector("slot[name=trigger]").addEventListener("click",()=>d()),l.querySelector(".scrim").addEventListener("pointerdown",s),{expose:{show:d,toggle:v,close:s},onMounted:()=>addEventListener("resize",s),onUnmounted:()=>removeEventListener("resize",s)}}}){}b.define(E);export{b as Popup};
//# sourceMappingURL=popup.js.map
Binary file modified public/fonts/KaTeX_AllInOne.woff2
Binary file not shown.
Binary file added public/fonts/KaTeX_AllInOne_Neg.woff2
Binary file not shown.
9 changes: 0 additions & 9 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -108,15 +108,6 @@ s-page {
overflow: hidden;
}

.plot-data.add-data {
position: relative;
padding-top: 15px;
padding-bottom: 15px;
margin-bottom: 50px;
display: flex;
gap: 5px;
}

.data-import {
position: absolute;
bottom: 20px;
Expand Down
18 changes: 10 additions & 8 deletions src/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type {
FunctionPlotDatum,
FunctionPlotOptions,
} from "function-plot";
import { cloneDeep } from "lodash-es";
import cloneDeep from "lodash-es/cloneDeep";

export type ValueLabel = { value: string; label: string; default?: boolean };

Expand Down Expand Up @@ -75,8 +75,8 @@ export type FnType = {
};

export type InternalDatum = Omit<FunctionPlotDatum, "fnType" | "graphType"> & {
fnType: "text" | FunctionPlotDatum["fnType"];
graphType: FunctionPlotDatum["graphType"];
fnType: "text" | NonNullable<FunctionPlotDatum["fnType"]>;
graphType: NonNullable<FunctionPlotDatum["graphType"]>;
key: number;
hidden?: boolean;
};
Expand Down Expand Up @@ -105,7 +105,8 @@ export function toOriginalDatum(items: InternalDatum[], forExport?: boolean) {
const graphType = item.graphType;
const graphTypeObj = getAllowedGraphType(fnType).find(
(item) => item.value === graphType
)!;
);
if (!graphTypeObj) throw new Error("graphType not found: " + graphType);
if (getFnType(fnType).default) {
delete (<any>item).fnType;
}
Expand Down Expand Up @@ -150,10 +151,11 @@ export const getFnType = (fnType: string = "linear") =>
<FnType>fnTypeArr.find(({ value }) => value === fnType);

/** graphType 字段选项 */
export const getAllowedGraphType = (fnType?: string) =>
fnType
? (fnTypeArr.find(({ value }) => value === fnType)?.allowedGraphType ?? [])
: [];
export const getAllowedGraphType = (fnType: string) => {
const fnTypeObj = fnTypeArr.find(({ value }) => value === fnType);
if (!fnTypeObj) throw new Error("fnType not found: " + fnType);
return fnTypeObj.allowedGraphType;
};

export const fnTypeArr = [
{
Expand Down
167 changes: 105 additions & 62 deletions src/editor/annotation.vue
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<template>
<div class="plot-data annotation">
<div class="selectors">
<s-segmented-button v-model.lazy="props.annotation.axis">
<s-segmented-button-item value="y">{{
t("annotation.horizontal")
}}</s-segmented-button-item>
<s-segmented-button v-model.lazy="self.variable">
<s-segmented-button-item value="y">
{{ t("annotation.horizontal") }}
</s-segmented-button-item>
<s-segmented-button-item value="x">
{{ t("annotation.vertical") }}
</s-segmented-button-item>
Expand All @@ -19,7 +19,7 @@
>
<SIconDelete />
</s-icon-button>
{{ t("buttons.del") }}
{{ t("annotation.topButton.delete") }}
</s-tooltip>
<s-tooltip>
<s-icon-button
Expand All @@ -29,108 +29,151 @@
>
<SIconTextfield />
</s-icon-button>
{{ t("annotation.text") }}
{{ t(`annotation.topButton.${showText ? "remove" : "add"}Text`) }}
</s-tooltip>
<span class="annotation-drag drag-icon">
<SIconDrag />
</span>
</div>
</div>

<div class="annotation-texts" :class="{ showText }">
<s-text-field
class="styled annotation-value"
type="number"
v-model="props.annotation.value"
:label="props.annotation.axis + '='"
></s-text-field>
<div class="annotation-fields" :class="{ showText }">
<div class="label-and-value">
<span class="label styled"> {{ self.variable + "=" }} </span>
<s-text-field
class="styled-inner value"
type="number"
v-model="self.value"
:label="t('annotation.value')"
@blur="handleValueBlur"
></s-text-field>
</div>
<Transition name="anntextslide">
<s-text-field
v-if="showText"
class="annotation-textfield"
class="text"
:label="t('annotation.text')"
v-model="props.annotation.text"
v-model="self.text"
></s-text-field>
</Transition>
</div>
</div>
</template>

<script setup lang="ts">
import { InternalAnnotation } from "@/consts";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
import { I18nSchema } from "@/i18n";
const { t } = useI18n<{ message: I18nSchema }>();

import SIconDelete from "@/ui/icons/delete.vue";
import SIconDrag from "@/ui/icons/drag.vue";
import SIconTextfield from "@/ui/icons/textfield.vue";

import { useProfile } from "@/states";
import { PrivateAnnotation } from "@/types/annotation";

import { ref, toRef, watch } from "vue";

const profile = useProfile();
const props = defineProps<{
index: number;
annotation: InternalAnnotation;
self: PrivateAnnotation;
}>();
const self = toRef(props, "self");

const showText = ref(self.value.text !== "");
watch(showText, (value) => {
if (!value) self.value.text = "";
});

import emitter from "@/mitt";
import { ref, watch } from "vue";
watch([() => props.annotation.axis, () => props.annotation.text], () =>

watch([() => self.value.variable, () => self.value.text], () =>
emitter.emit("require-full-update", "annotations axis change")
);

import SIconDelete from "@/ui/icons/delete.vue";
import SIconDrag from "@/ui/icons/drag.vue";
import SIconTextfield from "@/ui/icons/textfield.vue";
function handleValueBlur() {
self.value.value = Number(self.value.value);
}

import { useProfile } from "@/states";
const profile = useProfile();
import { Snackbar } from "sober";
function deleteAnnotation() {
emitter.emit("require-full-update", "annotations axis change");
const backup = props.self;
profile.annotations.splice(props.index, 1);
emitter.emit("require-full-update", "annotations axis change");
profile.datum.splice(props.index, 1);
Snackbar.builder({
text: t("editor.delete.success"),
action: {
text: t("editor.delete.undo"),
click: () => {
profile.annotations.splice(props.index, 0, backup);
emitter.emit("require-full-update", "annotations axis change");
},
},
});
}

const showText = ref(props.annotation.text !== "");

watch(showText, (value) => {
if (!value) props.annotation.text = "";
});
</script>

<style>
<style lang="scss">
.plot-data.annotation {
display: flex;
flex-direction: column;
}
.annotation-value {
font-size: 20px;
}
.annotation-textfield {
font-size: 16px;
}
.annotation-texts {

.annotation-fields {
display: flex;
align-items: center;
gap: 10px;
padding-top: 8px;
overflow: hidden;
}
.annotation-texts s-text-field {
width: 0;
flex-grow: 1;
}
</style>

<style>
.anntextslide-enter-from,
.anntextslide-leave-to {
flex-grow: 0 !important;
margin-left: -10px;
s-text-field {
width: 0;
flex-grow: 1;
}
.label-and-value {
display: flex;
align-items: center;
flex-grow: 1;
gap: 3px;
.label {
font-size: 25px;
width: 1.9em;
text-align: right;
margin-bottom: -0.1em;
}
.value {
font-size: 22px;
}
}
.text {
font-size: 16px;
flex-grow: 2;
}
}

.anntextslide-leave-active {
transition:
flex-grow var(--s-motion-duration-medium1) var(--s-motion-easing-emphasized),
margin-left var(--s-motion-duration-medium1)
var(--s-motion-easing-emphasized) 0.2s;
}
.anntextslide {
&-enter-from,
&-leave-to {
flex-grow: 0 !important;
margin-left: -10px;
}

&-leave-active {
transition:
flex-grow var(--s-motion-duration-medium1)
var(--s-motion-easing-emphasized),
margin-left var(--s-motion-duration-medium1)
var(--s-motion-easing-emphasized) 0.2s;
}

.anntextslide-enter-active {
transition:
flex-grow var(--s-motion-duration-medium1) var(--s-motion-easing-emphasized),
margin-left var(--s-motion-duration-medium1)
var(--s-motion-easing-emphasized);
&-enter-active {
transition:
flex-grow var(--s-motion-duration-medium1)
var(--s-motion-easing-emphasized),
margin-left var(--s-motion-duration-medium1)
var(--s-motion-easing-emphasized);
}
}
</style>
Loading