Skip to content

Commit 5ba0b94

Browse files
committed
feat: Sync settings and stock per-project
1 parent 0adc5a5 commit 5ba0b94

34 files changed

+347
-193
lines changed

web/components/BomTab.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type { BoardLayoutLeftover } from '@aklinker1/cutlist';
44
const url = useAssemblyUrl();
55
const { data: doc } = useDocumentQuery(url);
66
const { data, isLoading } = useBoardLayoutsQuery();
7-
const distanceUnit = useDistanceUnit();
7+
const { distanceUnit } = useProjectSettings();
88
const formatDistance = useFormatDistance();
99
1010
const rows = computed(() => {

web/components/PartListItem.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const fontSize = usePx(() =>
2121
),
2222
);
2323
24-
const showPartNumbers = useShowPartNumbers();
24+
const { showPartNumbers } = useProjectSettings();
2525
</script>
2626

2727
<template>

web/components/ProjectSidebar.vue

+7-9
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
<script lang="ts" setup>
22
import type { HorizontalNavigationLink } from '#ui/types';
33
4-
const url = useAssemblyUrl();
5-
const { data: doc } = useDocumentQuery();
6-
const { isFetching: isFetchingLayouts } = useBoardLayoutsQuery();
7-
const { data: boardLayouts } = useBoardLayoutsQuery();
4+
const { data: boardLayouts, isFetching: isFetchingLayouts } =
5+
useBoardLayoutsQuery();
86
const refresh = useRefreshOnshapeQueries();
97
108
const warningsBadge = computed(() => {
@@ -21,10 +19,10 @@ const links = computed<HorizontalNavigationLink[]>(() => [
2119
click: () => void (tab.value = 'bom'),
2220
},
2321
{
24-
label: 'Stock',
25-
icon: 'i-heroicons-truck',
26-
active: tab.value === 'stock',
27-
click: () => void (tab.value = 'stock'),
22+
label: 'Boards',
23+
icon: 'i-fluent-emoji-high-contrast-wood',
24+
active: tab.value === 'boards',
25+
click: () => void (tab.value = 'boards'),
2826
},
2927
{
3028
label: 'Warnings',
@@ -92,7 +90,7 @@ const editProject = useEditProject();
9290
<div class="relative flex-1">
9391
<div class="absolute inset-0 overflow-auto">
9492
<BomTab v-if="tab === 'bom'" />
95-
<StockTab v-else-if="tab === 'stock'" />
93+
<StockTab v-else-if="tab === 'boards'" />
9694
<WarningsTab v-else-if="tab === 'warnings'" class="p-8" />
9795
<SettingsTab v-else-if="tab === 'settings'" class="p-8" />
9896
</div>

web/components/SettingsTab.vue

+39-14
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,55 @@
11
<script lang="ts" setup>
22
import { Distance } from '@aklinker1/cutlist';
33
4-
const optimize = useOptimizeForSetting();
5-
const bladeWidth = useBladeWidthSetting();
6-
const distanceUnit = useDistanceUnit();
7-
const showPartNumbers = useShowPartNumbers();
8-
const extraSpace = useExtraSpaceSetting();
4+
const {
5+
bladeWidth,
6+
distanceUnit,
7+
extraSpace,
8+
optimize,
9+
showPartNumbers,
10+
isLoading,
11+
changes,
12+
resetSettings: resetLocal,
13+
} = useProjectSettings();
914
1015
// Convert values when units change
1116
watch(distanceUnit, (newUnit, oldUnit) => {
1217
if (!newUnit || !oldUnit) return;
1318
14-
const convertValue = (value: Ref<string | number>) => {
19+
const convertDistance = (value: Ref<string | number | undefined>) => {
20+
if (value.value == null) return;
1521
const dist = new Distance(value.value + oldUnit);
1622
value.value = dist[newUnit];
1723
};
18-
convertValue(bladeWidth);
19-
convertValue(extraSpace);
24+
convertDistance(bladeWidth);
25+
convertDistance(extraSpace);
2026
});
27+
28+
const projectId = useProjectId();
29+
const { mutate: _save, isPending: isSaving } = useSetSettingsMutation();
30+
function save() {
31+
_save({
32+
projectId: projectId.value,
33+
changes: toRaw(changes.value),
34+
});
35+
}
36+
37+
const { mutate: _reset, isPending: isResetting } = useDeleteSettingsMutation();
38+
function reset() {
39+
_reset(projectId.value, {
40+
onSettled: () => resetLocal(),
41+
});
42+
}
2143
</script>
2244

2345
<template>
24-
<div class="flex flex-col gap-4">
46+
<form v-if="!isLoading" class="flex flex-col gap-4" @submit.prevent="save">
2547
<UFormGroup label="Distance unit">
2648
<USelect v-model="distanceUnit" :options="['in', 'm', 'mm']" />
2749
</UFormGroup>
2850

2951
<UFormGroup :label="`Blade width (${distanceUnit}):`">
30-
<UInput v-model="bladeWidth" type="number" />
52+
<UInput v-model="bladeWidth" type="number" min="0" step="0.00001" />
3153
</UFormGroup>
3254

3355
<UFormGroup :label="`Extra space (${distanceUnit}):`">
@@ -40,8 +62,11 @@ watch(distanceUnit, (newUnit, oldUnit) => {
4062

4163
<UCheckbox v-model="showPartNumbers" label="Show part numbers in preview" />
4264

43-
<p class="text-sm opacity-50 text-center pt-8">
44-
Settings are saved when returning to the website.
45-
</p>
46-
</div>
65+
<div class="flex flex-row-reverse gap-4">
66+
<UButton type="submit" :loading="isSaving">Save Changes</UButton>
67+
<UButton color="gray" :loading="isResetting" @click="reset"
68+
>Reset</UButton
69+
>
70+
</div>
71+
</form>
4772
</template>

web/components/StockMatrixInput.vue

+36-34
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,48 @@
11
<script lang="ts" setup>
2-
import { StockMatrix } from '@aklinker1/cutlist';
3-
import { z } from 'zod';
4-
import YAML from 'js-yaml';
2+
import { reduceStockMatrix } from '@aklinker1/cutlist';
53
6-
const model = defineModel<StockMatrix[]>();
4+
const value = defineModel<string>({ required: true });
75
8-
const getModelString = () =>
9-
YAML.dump(model.value, { indent: 2, flowLevel: 2 });
10-
11-
const text = ref(getModelString());
6+
const internalValue = ref(value.value);
7+
onMounted(() => {
8+
internalValue.value = value.value;
9+
});
1210
13-
const textModel = computed<string>({
14-
get() {
15-
return text.value;
16-
},
17-
set(value) {
11+
const parseStock = useParseStock();
12+
const err = ref<unknown>();
13+
watchThrottled(
14+
internalValue,
15+
(internalValue) => {
1816
try {
19-
text.value = value;
20-
model.value = z.array(StockMatrix).parse(YAML.load(value));
21-
error.value = undefined;
22-
} catch (err) {
23-
error.value = err;
17+
console.log(1);
18+
const stock = reduceStockMatrix(parseStock(internalValue));
19+
console.log(2, stock);
20+
value.value = internalValue;
21+
err.value = undefined;
22+
} catch (error) {
23+
err.value = error;
24+
console.error(error);
2425
}
2526
},
26-
});
27-
28-
const error = ref<unknown>();
27+
{
28+
throttle: 500,
29+
leading: false,
30+
trailing: true,
31+
},
32+
);
2933
</script>
3034

3135
<template>
32-
<div class="absolute inset-0 flex flex-col p-4 overlfow-auto gap-4">
33-
<textarea
34-
v-model="textModel"
35-
class="font-mono flex-1 resize-none bg-white dark:bg-gray-900 border border-gray-300 dark:border-gray-700 p-4 outline-none rounded-lg whitespace-pre"
36-
/>
37-
<div
38-
v-if="error"
39-
class="bg-red-900 shrink-0 p-4 rounded-lg border border-red-600"
40-
>
41-
<p class="text-white whitespace-pre-wrap">
42-
{{ error }}
43-
</p>
44-
</div>
36+
<textarea
37+
v-model="internalValue"
38+
class="font-mono flex-1 resize-none bg-white dark:bg-gray-900 border border-gray-300 dark:border-gray-700 p-4 outline-none rounded-lg whitespace-pre"
39+
/>
40+
<div
41+
v-if="err"
42+
class="bg-red-900 shrink-0 p-4 rounded-lg border border-red-600"
43+
>
44+
<p class="text-white whitespace-pre-wrap">
45+
{{ err }}
46+
</p>
4547
</div>
4648
</template>

web/components/StockTab.vue

+27-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,33 @@
11
<script lang="ts" setup>
2-
const { data } = useBoardLayoutsQuery();
2+
const { stock, resetStock } = useProjectSettings();
33
4-
const stock = useStock();
4+
const projectId = useProjectId();
5+
const { mutate: _save, isPending: isSaving } = useSetSettingsMutation();
6+
function save() {
7+
console.log('SAVING...');
8+
_save({
9+
projectId: projectId.value,
10+
changes: { stock: stock.value },
11+
});
12+
}
13+
14+
const { mutate: _reset, isPending: isResetting } = useDeleteSettingsMutation();
15+
function reset() {
16+
_reset(projectId.value, {
17+
onSettled: () => resetStock(),
18+
});
19+
}
520
</script>
621

722
<template>
8-
<StockMatrixInput v-model="stock" />
23+
<div class="absolute inset-0 flex flex-col p-4 gap-4">
24+
<StockMatrixInput v-if="stock != null" v-model="stock" />
25+
26+
<div class="shrink-0 flex flex-row-reverse gap-4">
27+
<UButton type="submit" :loading="isSaving" @click="save">Save</UButton>
28+
<UButton color="gray" :loading="isResetting" @click="reset"
29+
>Reset</UButton
30+
>
31+
</div>
32+
</div>
933
</template>

web/composables/useBladeWidth.ts

-10
This file was deleted.

web/composables/useBladeWidthSetting.ts

-8
This file was deleted.

web/composables/useBoardLayoutsQuery.ts

+24-5
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
import { useQuery } from '@tanstack/vue-query';
2-
import { generateBoardLayouts } from '@aklinker1/cutlist';
2+
import {
3+
Distance,
4+
generateBoardLayouts,
5+
type Config,
6+
} from '@aklinker1/cutlist';
37

48
export default function () {
59
const loader = useOnshapeLoader();
610
const url = useAssemblyUrl();
7-
const config = useCutlistConfig();
8-
const stock = useStock();
11+
const { bladeWidth, optimize, extraSpace, distanceUnit, stock } =
12+
useProjectSettings();
13+
const parseStock = useParseStock();
914

1015
const partsQuery = useQuery({
1116
queryKey: ['onshape', 'board-layouts', url],
@@ -15,9 +20,23 @@ export default function () {
1520

1621
const layouts = computed(() => {
1722
const parts = partsQuery.data.value;
18-
if (parts == null) return undefined;
23+
if (
24+
parts == null ||
25+
bladeWidth.value == null ||
26+
extraSpace.value == null ||
27+
optimize.value == null ||
28+
distanceUnit.value == null ||
29+
stock.value == null
30+
)
31+
return;
1932

20-
return generateBoardLayouts(toRaw(parts), stock.value, config.value);
33+
const config: Config = {
34+
bladeWidth: new Distance(bladeWidth.value + distanceUnit.value).m,
35+
extraSpace: new Distance(extraSpace.value + distanceUnit.value).m,
36+
optimize: optimize.value === 'Cuts' ? 'cuts' : 'space',
37+
precision: 1e-5,
38+
};
39+
return generateBoardLayouts(toRaw(parts), parseStock(stock.value), config);
2140
});
2241

2342
return {

web/composables/useCutlistConfig.ts

-14
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { useMutation, useQueryClient } from '@tanstack/vue-query';
2+
3+
export default function () {
4+
const accountService = useAccountService();
5+
const client = useQueryClient();
6+
7+
return useMutation({
8+
mutationFn(projectId: string | undefined) {
9+
return accountService.value.deleteSettings(projectId);
10+
},
11+
onSettled() {
12+
client.invalidateQueries({
13+
queryKey: ['settings'],
14+
});
15+
},
16+
});
17+
}

web/composables/useDistanceUnit.ts

-3
This file was deleted.

web/composables/useExtraSpace.ts

-10
This file was deleted.

web/composables/useExtraSpaceSetting.ts

-8
This file was deleted.

0 commit comments

Comments
 (0)