Skip to content

Commit 91a91f4

Browse files
committed
Add support to load local toolkit manifest at the start of installation
1 parent 4e2aa5b commit 91a91f4

File tree

20 files changed

+504
-158
lines changed

20 files changed

+504
-158
lines changed

locales/en-US.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@
101101
"pre_update_note": "updating toolkit version to '%{target_version}' (currently '%{current_version}'), this will replace your current toolkit after installation.",
102102
"continue": "Continue",
103103
"back": "Back",
104+
"native": "Native",
104105
"customize": "Customize",
105106
"show_ui": "Show UI",
106107
"quit": "Quit",
@@ -171,7 +172,7 @@
171172
"install_dir_is_os_dir": "the program is installed but the installation directory appears to be an OS specific directory '%{path}', try re-install this program to fix the problem",
172173
"uninstall_self_residual_info": "uninstall finished successfully, some files might not get removed until next system reboot",
173174
"duplicated_config_files": "duplicated config file detected at these locations:\n1. %{first}\n2. %{second}\nconfiguration of the first one will be used",
174-
"install_other_edition": "Install other edition",
175+
"install_using_toolkit_manifest": "Install Using Toolkit Manifest",
175176
"configuration": "Configuration",
176177
"customization": "Customization",
177178
"confirmation": "Confirmation",

locales/zh-CN.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@
9797
"pre_update_note": "即将升级套件版本至 '%{target_version}' (当前 '%{current_version}'),此举将会覆盖您当前已安装的工具套件。",
9898
"continue": "继续",
9999
"back": "返回",
100+
"native": "原生",
100101
"customize": "自定义",
101102
"show_ui": "显示界面",
102103
"quit": "退出",
@@ -167,7 +168,7 @@
167168
"install_dir_is_os_dir": "程序已安装,但其安装目录位于系统专用路径 '%{path}',建议检查或重新安装。",
168169
"uninstall_self_residual_info": "卸载已完成,某些文件将在系统重启后彻底移除。",
169170
"duplicated_config_files": "检查到以下重复配置文件:\n1. %{first}\n2. %{second}\n将读取第一个文件",
170-
"install_other_edition": "安装其他版本",
171+
"install_using_toolkit_manifest": "使用套件清单安装",
171172
"configuration": "安装配置",
172173
"customization": "组件定制",
173174
"confirmation": "确认信息",

rim_gui/src-tauri/src/installer_mode.rs

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use rim_common::{build_config, exe};
77
use serde::{Deserialize, Serialize};
88
use tauri::AppHandle;
99
use tokio::sync::Mutex;
10+
use url::Url;
1011

1112
use super::{common, INSTALL_DIR};
1213
use crate::common::BaseConfiguration;
@@ -92,29 +93,26 @@ fn toolkit_name() -> String {
9293

9394
// Make sure this function is called first after launch.
9495
#[tauri::command]
95-
async fn load_manifest_and_ret_version() -> Result<String> {
96+
async fn load_manifest_and_ret_version(path: Option<PathBuf>) -> Result<String> {
9697
// TODO: Give an option for user to specify another manifest.
9798
// note that passing command args currently does not work due to `windows_subsystem = "windows"` attr
98-
let mut manifest = get_toolkit_manifest(None, false).await?;
99+
let path_url = path.as_ref().map(|p| Url::from_file_path(p))
100+
.transpose()
101+
.map_err(|_| anyhow!("unable to convert path '{path:?}' to URL"))?;
102+
let mut manifest = get_toolkit_manifest(path_url, false).await?;
99103
manifest.adjust_paths()?;
100104
let version = manifest.version.clone().unwrap_or_default();
101105

102-
if TOOLSET_MANIFEST.set(Mutex::new(manifest)).is_err() {
103-
error!(
104-
"unable to set initialize manifest to desired one \
105-
as it was already initialized somewhere else, \
106-
returning the cached version instead"
107-
);
108-
Ok(cached_manifest()
109-
.lock()
110-
.await
111-
.version
112-
.clone()
113-
.unwrap_or_default())
106+
if let Some(existing) = TOOLSET_MANIFEST.get() {
107+
// update the existing manifest
108+
*existing.lock().await = manifest;
109+
debug!("manifest updated");
114110
} else {
111+
// This should be fine as the cell is garanteed to have no previous value.
112+
_ = TOOLSET_MANIFEST.set(Mutex::new(manifest));
115113
debug!("manifest loaded");
116-
Ok(version)
117114
}
115+
Ok(version)
118116
}
119117

120118
#[derive(Debug, Serialize, Deserialize)]

rim_gui/src/components/BaseButton.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const themeClasses = computed(() => {
3131
<button p="x-3% y-1%" :class="[
3232
themeClasses,
3333
'rounded-[30vw] b-none hover:op-80', // Common classes
34+
{ 'cursor-pointer': !disabled }, // Disabled styles
3435
{ 'opacity-50 cursor-not-allowed': disabled }, // Disabled styles
3536
]" :disabled="disabled">
3637
<slot></slot>
@@ -45,7 +46,6 @@ button {
4546
white-space: nowrap;
4647
overflow: hidden;
4748
text-overflow: ellipsis;
48-
cursor: pointer;
4949
transition:
5050
background-color 0.3s,
5151
border-color 0.3s;

rim_gui/src/components/BaseCheckBox.vue

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,6 @@ const toggleCheck = () => {
1919
2020
isChecked.value = !isChecked.value;
2121
};
22-
23-
function titleClick() {
24-
emit('titleClick');
25-
}
2622
</script>
2723

2824
<template>
@@ -32,14 +28,14 @@ function titleClick() {
3228
'c-active': isGroup,
3329
'bg-active border-active': isChecked,
3430
'bg-disabled-bg': disabled,
35-
'hover:b-active': !isChecked && !disabled,
31+
'hover:b-active': !disabled,
3632
'cursor-not-allowed': disabled,
3733
}" @click="toggleCheck">
3834
<slot name="icon">
3935
<i class="i-mdi:check" v-if="isChecked" c="active" />
4036
</slot>
4137
</span>
42-
<span @click="titleClick" whitespace-nowrap>
38+
<span @click="emit('titleClick')" whitespace-nowrap>
4339
<slot>
4440
<component v-if="labelComponent" :is="labelComponent" v-bind="labelComponentProps" />
4541
<span :class="isGroup ? 'cb-label-group' : 'cb-label'" v-else>{{ title }}</span>
@@ -54,8 +50,8 @@ function titleClick() {
5450
display: flex;
5551
align-items: center;
5652
justify-content: center;
57-
height: 1rem;
58-
width: 1rem;
53+
min-width: 1rem;
54+
min-height: 1rem;
5955
background: white;
6056
border: 2px solid rgb(204, 204, 204);
6157
border-radius: 3px;
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
<script setup lang="ts">
2+
import { computed, PropType, ref } from 'vue';
3+
4+
export interface DropdownItem {
5+
value: string;
6+
label: string;
7+
}
8+
9+
const props = defineProps({
10+
items: {
11+
type: Array as PropType<DropdownItem[]>,
12+
required: true,
13+
default: () => []
14+
},
15+
modelValue: {
16+
type: String as PropType<string | null>,
17+
default: null
18+
},
19+
placeholder: {
20+
type: String,
21+
default: ''
22+
},
23+
width: {
24+
type: String,
25+
default: 'auto'
26+
},
27+
height: {
28+
type: String,
29+
default: 'auto'
30+
},
31+
default: {
32+
type: Object,
33+
default: null
34+
}
35+
});
36+
37+
const emit = defineEmits(['update:modelValue']);
38+
39+
const isOpen = ref(false);
40+
const menuRef = ref<HTMLElement | null>(null);
41+
42+
const selectedItem = computed(() =>
43+
props.items.find(item => item.value === props.modelValue)
44+
);
45+
46+
47+
function toggleDropdown() {
48+
isOpen.value = !isOpen.value;
49+
}
50+
51+
function selectItem(value: string | number) {
52+
emit('update:modelValue', value);
53+
isOpen.value = false;
54+
}
55+
56+
function closeOnClickOutside(event: MouseEvent) {
57+
if (menuRef.value && !menuRef.value.contains(event.target as Node)) {
58+
isOpen.value = false;
59+
}
60+
}
61+
62+
// Close dropdown when clicking outside
63+
document.addEventListener('click', closeOnClickOutside);
64+
</script>
65+
66+
<template>
67+
<div class="dropdown" ref="menuRef">
68+
<div class="dropdown-toggle" :class="{ 'dropdown-open': isOpen }" @click.stop="toggleDropdown" :style="{
69+
width: width,
70+
height: height,
71+
}">
72+
<span>{{ selectedItem ? selectedItem.label : placeholder }}</span>
73+
<svg class="dropdown-arrow" viewBox="0 0 24 24" :class="{ rotated: isOpen }">
74+
<path d="M7 10l5 5 5-5z" />
75+
</svg>
76+
</div>
77+
78+
<Transition name="dropdown">
79+
<ul v-show="isOpen" class="dropdown-menu">
80+
<li v-for="item in items" :key="item.value" class="dropdown-item"
81+
:class="{ selected: modelValue === item.value }" @click="selectItem(item.value)">
82+
{{ item.label }}
83+
</li>
84+
</ul>
85+
</Transition>
86+
</div>
87+
</template>
88+
89+
<style scoped>
90+
.dropdown {
91+
position: relative;
92+
margin: 1rem;
93+
height: 100%;
94+
width: 100%;
95+
}
96+
97+
.dropdown-toggle {
98+
display: flex;
99+
justify-content: space-between;
100+
align-items: center;
101+
cursor: pointer;
102+
transition: border-color 0.3s;
103+
background: rgba(255, 255, 255, .7);
104+
border: 1px solid rgba(0, 0, 0, .1);
105+
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
106+
backdrop-filter: url(#frosted);
107+
-webkit-backdrop-filter: blur(25px);
108+
font-size: clamp(100%, 2vh, 20px);
109+
padding: 0.3rem 1rem;
110+
box-sizing: border-box;
111+
border-radius: 20px;
112+
}
113+
114+
.dropdown-toggle:hover {
115+
--uno: 'b-base';
116+
}
117+
118+
.dropdown-toggle.dropdown-open {
119+
--uno: 'b-active';
120+
}
121+
122+
.dropdown-arrow {
123+
width: 16px;
124+
height: 16px;
125+
transition: transform 0.3s;
126+
}
127+
128+
.dropdown-arrow.rotated {
129+
transform: rotate(180deg);
130+
}
131+
132+
.dropdown-menu {
133+
position: absolute;
134+
top: 100%;
135+
left: 0;
136+
right: 0;
137+
margin-top: 4px;
138+
padding: 0;
139+
border: 1px solid #ddd;
140+
border-radius: 10px;
141+
background-color: white;
142+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
143+
list-style: none;
144+
z-index: 100;
145+
max-height: 200px;
146+
overflow-y: auto;
147+
}
148+
149+
.dropdown-item {
150+
padding: 8px 12px;
151+
cursor: pointer;
152+
transition: background-color 0.2s;
153+
}
154+
155+
.dropdown-item:hover {
156+
background-color: #f1f1f1;
157+
}
158+
159+
.dropdown-item.selected {
160+
background-color: #eef5ff;
161+
--uno: 'c-active'
162+
}
163+
164+
/* Transition effects */
165+
.dropdown-enter-active,
166+
.dropdown-leave-active {
167+
transition: all 0.3s ease;
168+
transform-origin: top center;
169+
}
170+
171+
.dropdown-enter-from,
172+
.dropdown-leave-to {
173+
opacity: 0;
174+
transform: scaleY(0.9);
175+
}
176+
177+
.dropdown-enter-to,
178+
.dropdown-leave-from {
179+
opacity: 1;
180+
transform: scaleY(1);
181+
}
182+
</style>
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<template>
2+
<transition name="panel">
3+
<div v-if="props.show" class="panel-backdrop" @click.self="emit('close')">
4+
<div class="panel-content" :style="{
5+
width: width,
6+
height: height,
7+
}">
8+
<slot></slot>
9+
</div>
10+
</div>
11+
</transition>
12+
</template>
13+
14+
<script setup lang="ts">
15+
const props = defineProps({
16+
show: {
17+
type: Boolean,
18+
required: true
19+
},
20+
width: {
21+
type: String,
22+
default: 'auto'
23+
},
24+
height: {
25+
type: String,
26+
default: 'auto'
27+
},
28+
});
29+
30+
const emit = defineEmits(['close']);
31+
</script>
32+
33+
<style scoped>
34+
.panel-backdrop {
35+
position: fixed;
36+
top: 0;
37+
left: 0;
38+
width: 100vw;
39+
height: 100vh;
40+
backdrop-filter: url(#frosted);
41+
-webkit-backdrop-filter: blur(25px);
42+
display: flex;
43+
justify-content: center;
44+
align-items: center;
45+
}
46+
47+
.panel-content {
48+
background: rgba(255, 255, 255, 0.85);
49+
margin-top: 6%;
50+
border-radius: 20px;
51+
box-shadow: 0 16px 32px rgba(0, 0, 0, .12);
52+
max-width: 90%;
53+
max-height: 75%;
54+
overflow: auto;
55+
padding: 2%;
56+
position: relative;
57+
}
58+
59+
/* Enter/leave animations */
60+
.panel-enter-active .panel-content,
61+
.panel-leave-active .panel-content {
62+
transition: all 0.3s ease;
63+
}
64+
65+
.panel-enter-from .panel-content,
66+
.panel-leave-to .panel-content {
67+
transform: scale(0.7);
68+
opacity: 0;
69+
}
70+
71+
.panel-enter-active,
72+
.panel-leave-active {
73+
transition: opacity 0.3s ease;
74+
}
75+
</style>

0 commit comments

Comments
 (0)