Skip to content

Commit

Permalink
✨ Add export config
Browse files Browse the repository at this point in the history
  • Loading branch information
mobyw committed Sep 21, 2024
1 parent 4a184b3 commit cc2fcb9
Show file tree
Hide file tree
Showing 5 changed files with 287 additions and 27 deletions.
4 changes: 4 additions & 0 deletions packages/app/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import ContentCard from "@/components/ContentCard.vue";
import BlocklyTab from "@/components/BlocklyTab.vue";
import TutorialTab from "@/components/TutorialTab.vue";
import CodeTab from "@/components/CodeTab.vue";
import ConfigTab from "@/components/ConfigTab.vue";
import ButtonPanel from "@/components/ButtonPanel.vue";
// Workspace
import { loadJson, generateCode } from "@/workspace";
Expand Down Expand Up @@ -61,6 +62,9 @@ onMounted(() => {
<template v-slot:tab-2>
<CodeTab />
</template>
<template v-slot:tab-3>
<ConfigTab />
</template>
</ContentCard>
<ButtonPanel />
</div>
Expand Down
27 changes: 3 additions & 24 deletions packages/app/src/components/ButtonPanel.vue
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<script setup lang="ts">
import { useTheme } from "vuetify";
import {
exportZip,
outputsStore,
loadJson,
saveJson,
outputsStore,
exportPress,
setWorkspaceTheme,
} from "@/workspace";
Expand Down Expand Up @@ -49,41 +49,20 @@ function toggleTheme() {
<v-btn color="tertiary" @click="saveJson">
<v-icon :icon="mdiContentSave"></v-icon>
暂存
<!-- SAVE -->
<v-tooltip activator="parent" location="bottom"> 暂存工作区 </v-tooltip>
</v-btn>

<v-btn color="tertiary" @click="loadJson">
<v-icon :icon="mdiFileRestore"></v-icon>
恢复
<!-- RESTORE -->
<v-tooltip activator="parent" location="bottom">
恢复保存的工作区
</v-tooltip>
</v-btn>

<v-btn color="tertiary" @click="">
<v-icon :icon="mdiFileDownload"></v-icon>
下载
<!-- RESTORE -->
<v-tooltip activator="parent" location="bottom">
下载工作区源代码
</v-tooltip>
</v-btn>

<v-btn color="tertiary" @click="">
<v-icon :icon="mdiFileUpload"></v-icon>
导入
<!-- RESTORE -->
<v-tooltip activator="parent" location="bottom">
导入工作区源代码
</v-tooltip>
</v-btn>

<v-btn color="tertiary" @click="exportZip">
<v-btn color="tertiary" @click="exportPress">
<v-icon :icon="mdiLanguagePython"></v-icon>
导出项目
<!-- RESTORE -->
<v-tooltip activator="parent" location="bottom">
导出 NoneBot 项目
</v-tooltip>
Expand Down
102 changes: 102 additions & 0 deletions packages/app/src/components/ConfigTab.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<script setup lang="ts">
import { outputsStore, exportZip } from "@/workspace";
const items = [
{ name: "console", description: "控制台机器人" },
{ name: "onebot", description: "OneBot V11 & V12" },
];
const itemProps = (item: { name: string; description: string }) => {
return {
title: item.name,
subtitle: item.description,
};
};
const nameRules = [
(value: string) => {
if (value) return true;
return "请填写项目名称";
},
];
const presetRules = [
(value: string) => {
if (value) return true;
return "请选择一个预设";
},
];
const portRules = [
(value: string) => {
if (!value) return "请填写端口";
if (!/^\d+$/.test(value)) return "端口必须为数字";
if (parseInt(value) < 1 || parseInt(value) > 65535)
return "端口范围为 1-65535";
return true;
},
];
</script>

<template>
<v-form>
<v-container>
<v-row>
<v-col cols="12" md="4">
<v-text-field
v-model="outputsStore.export.name"
:counter="10"
:rules="nameRules"
label="项目名称"
required
></v-text-field>
</v-col>
<v-col cols="12" md="4">
<v-select
v-model="outputsStore.export.preset"
:item-props="itemProps"
:items="items"
:rules="presetRules"
label="预设"
required
></v-select>
</v-col>
<v-col cols="12" md="4">
<v-text-field
v-model="outputsStore.export.port"
:rules="portRules"
label="端口"
required
></v-text-field>
</v-col>
</v-row>

<v-row>
<v-col cols="12" md="4">
<v-checkbox
v-model="outputsStore.export.platform"
label="包含 Windows 环境配置脚本"
value="windows"
hide-details
></v-checkbox>
</v-col>
<v-col cols="12" md="4">
<v-checkbox
v-model="outputsStore.export.platform"
label="包含 Linux 环境配置脚本"
value="linux"
hide-details
></v-checkbox>
</v-col>
</v-row>

<v-row>
<v-col cols="12" md="12">
<v-btn class="mt-4" @click="exportZip" block>
导出 NoneBot 项目
</v-btn>
</v-col>
</v-row>
</v-container>
</v-form>
</template>
9 changes: 9 additions & 0 deletions packages/app/src/components/ContentCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<v-tab value="tab-0"> 编程 </v-tab>
<v-tab value="tab-1"> 教程 </v-tab>
<v-tab value="tab-2"> 代码 </v-tab>
<v-tab value="tab-3"> 配置 </v-tab>
</v-tabs>
</v-toolbar>

Expand Down Expand Up @@ -37,6 +38,14 @@
</div>
</v-card>
</v-window-item>

<v-window-item value="tab-3">
<v-card>
<div class="content-tab">
<slot name="tab-3" />
</div>
</v-card>
</v-window-item>
</v-window>
</v-card>
</template>
Expand Down
172 changes: 169 additions & 3 deletions packages/app/src/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ export const outputsStore = reactive({
snackbarMsg: "" as string,
snackbarTimeout: 2500 as number,
snackbarColor: "green" as string,
export: {
name: "app" as string,
preset: { name: "console", description: "控制台机器人" },
port: 8080 as number,
platform: ["windows", "linux"] as string[],
},
});

export function setWorkspaceTheme(theme: string) {
Expand Down Expand Up @@ -111,15 +117,175 @@ export function generateCode() {
outputsStore.code = pythonGenerator.workspaceToCode(workspace);
}

export function exportPress() {
outputsStore.activeTab = "tab-3";
}

function generatePyproject(code: string, preset: string) {
const dependencies = new Set<string>([
"nonebot2[fastapi,httpx,websockets]>=2.3.3",
"nb-cli>=1.4.2",
]);
const importLines = code.split("\n\n")[0].split("\n");
importLines.forEach((line) => {
if (line.startsWith("from nonebot_plugin_alconna")) {
dependencies.add("nonebot-plugin-alconna>=0.52.3");
} else if (line.startsWith("from nonebot_plugin_apscheduler")) {
dependencies.add("nonebot-plugin-apscheduler>=0.5.0");
} else if (line.startsWith("import nonebot_plugin_localstore")) {
dependencies.add("nonebot-plugin-localstore>=0.7.1");
}
});
let adapters = "";
if (preset === "console") {
adapters = '{ name = "Console", module_name = "nonebot.adapters.console" }';
dependencies.add("nonebot-adapter-console>=0.6.0");
} else if (preset === "onebot") {
adapters =
'{ name = "OneBot V11", module_name = "nonebot.adapters.onebot.v11" }';
adapters +=
', { name = "OneBot V12", module_name = "nonebot.adapters.onebot.v12" }';
dependencies.add("nonebot-adapter-onebot>=2.4.5");
}
return `\
[project]
name = "noneblockly-app"
version = "0.1.0"
description = "NoneBot project generated by NoneBlockly"
authors = [{name = "name", email = "[email protected]"}]
dependencies = [
${Array.from(dependencies)
.map((dep) => `"${dep}"`)
.join(", \n")}
]
requires-python = ">=3.9"
license = {text = "MIT"}
[tool.nonebot]
adapters = [
{ name = "Console", module_name = "nonebot.adapters.console" }
]
plugin_dirs = ["plugins"]`;
}

function generateEnv(port: number) {
return `\
DRIVER=~fastapi+~httpx+~websockets
PORT=${port}`;
}

const windowsScripts = {
install: `\
# Step 1: Check if 'uv' is installed
$uvVersion = try {
uv --version
} catch {
$null
}
if ($uvVersion) {
Write-Host "UV is installed. Version: "
Write-Host $uvVersion
} else {
# Step 2: If 'uv' is not installed, ask user for confirmation to install
Write-Host "UV is not installed on this system."
$confirmation = Read-Host "Do you want to install UV? (Press Enter to confirm or type 'n' to cancel)"
if ($confirmation -eq '') {
Write-Host "Installing UV..."
Invoke-RestMethod https://astral.sh/uv/install.ps1 | Invoke-Expression
Write-Host "UV has been installed successfully."
} else {
Write-Host "Installation canceled."
exit
}
}
# Step 3: Create a Python virtual environment
Write-Host "Creating a Python virtual environment with Python 3.12..."
uv venv --python 3.12
Write-Host "Python virtual environment created successfully."
# Step 4: Install dependencies
uv pip install -r pyproject.toml`,
run: `\
$uvVersion = try {
uv --version
} catch {
$null
}
if ($null -eq $uvVersion) {
Write-Host "Please run 'install.ps1' first."
exit
}
uv run nb run`,
};

const linuxScripts = {
install: `\
#!/bin/bash
# Step 1: Check if 'uv' is installed
if command -v uv &> /dev/null
then
echo "UV is installed. Version info:"
uv --version
else
# Step 2: If 'uv' is not installed, ask user for confirmation to install
echo "UV is not installed on this system."
read -p "Do you want to install UV? (Press Enter to confirm or type 'n' to cancel): " confirmation
if [ "$confirmation" == "" ]; then
echo "Installing UV..."
curl -LsSf https://astral.sh/uv/install.sh | sh
echo "UV has been installed successfully."
else
echo "Installation canceled."
exit 1
fi
fi
# Step 3: Create a Python virtual environment
echo "Creating a Python virtual environment with Python 3.12..."
uv venv --python 3.12
echo "Python virtual environment created successfully."`,
run: `\
if ! command -v uv &> /dev/null
then
echo "Please run 'install.sh' first"
exit
fi
uv run nb run`,
};

export function exportZip() {
let workspace = Blockly.getMainWorkspace();
let zip = new JSZip();
let workspace = Blockly.getMainWorkspace();
let code = pythonGenerator.workspaceToCode(workspace);
zip.file("plugins/plugin_example.py", code);
zip.file("plugins/plugin_exported.py", code);
zip.file(
"pyproject.toml",
generatePyproject(code, outputsStore.export.preset.name),
);
zip.file(".env.prod", generateEnv(outputsStore.export.port));
outputsStore.export.platform.forEach((platform) => {
if (platform === "windows") {
zip.file("install.ps1", windowsScripts.install);
zip.file("run.ps1", windowsScripts.run);
} else if (platform === "linux") {
zip.file("install.sh", linuxScripts.install);
zip.file("run.sh", linuxScripts.run);
}
});
outputsStore.snackbarColor = "green";
outputsStore.snackbarMsg = "😎 已导出 Python 项目";
outputsStore.snackbar = true;
zip.generateAsync({ type: "blob" }).then(function (content) {
saveAs(content, "noneblockly.zip");
saveAs(content, `${outputsStore.export.name}.zip`);
});
}

0 comments on commit cc2fcb9

Please sign in to comment.