Skip to content

Commit

Permalink
feat: add all networks to offchain treasuries (#841)
Browse files Browse the repository at this point in the history
* feat: support null in Combobox

* refactor: use UiSelectorNetwork with definition

* feat: add UiSelectorNetwork to UiForm

* feat: add all networks to offchain treasuries

* feat: add Starknet networks to offchain networks

* fix: reset query on enter press

* fix: clear query on focus

* feat: add empty results message
  • Loading branch information
Sekhmet authored Oct 8, 2024
1 parent bf044c7 commit 136097a
Show file tree
Hide file tree
Showing 16 changed files with 181 additions and 104 deletions.
2 changes: 1 addition & 1 deletion apps/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"@ethersproject/units": "^5.7.0",
"@ethersproject/wallet": "^5.7.0",
"@headlessui-float/vue": "^0.15.0",
"@headlessui/vue": "^1.7.16",
"@headlessui/vue": "^1.7.23",
"@openzeppelin/merkle-tree": "^1.0.5",
"@snapshot-labs/eslint-config-vue": "^0.1.0-beta.18",
"@snapshot-labs/highlight": "^0.1.0-beta.2",
Expand Down
21 changes: 16 additions & 5 deletions apps/ui/src/components/Combobox.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ import {
import { Float } from '@headlessui-float/vue';
import { DefinitionWithOptions } from '@/types';
const NULL_SYMBOL = Symbol('null');
defineOptions({ inheritAttrs: false });
const model = defineModel<T>({ required: true });
const model = defineModel<T | null>({ required: true });
const props = defineProps<{
error?: string;
definition: DefinitionWithOptions<T>;
definition: DefinitionWithOptions<T | null>;
}>();
const dirty = ref(false);
Expand All @@ -37,7 +39,7 @@ const inputValue = computed({
return model.value;
},
set(newValue: T) {
set(newValue: T | null) {
dirty.value = true;
model.value = newValue;
}
Expand All @@ -46,6 +48,7 @@ const inputValue = computed({
function handleFocus(event: FocusEvent, open: boolean) {
if (!event.target || open) return;
query.value = '';
(event.target as HTMLInputElement).select();
}
Expand All @@ -66,7 +69,7 @@ watch(model, () => {
:dirty="dirty"
class="relative mb-[14px]"
>
<Combobox v-slot="{ open }" v-model="inputValue" as="div">
<Combobox v-slot="{ open }" v-model="inputValue" as="div" nullable>
<Float adaptive-width strategy="fixed" placement="bottom-end">
<div>
<ComboboxButton class="w-full">
Expand All @@ -78,6 +81,7 @@ watch(model, () => {
autocomplete="off"
:placeholder="definition.examples?.[0]"
:display-value="item => getDisplayValue(item as T)"
@keydown.enter="() => (query = '')"
@change="e => (query = e.target.value)"
@focus="event => handleFocus(event, open)"
/>
Expand All @@ -91,10 +95,17 @@ watch(model, () => {
class="w-full bg-skin-border rounded-b-lg border-t-skin-text/10 border shadow-xl overflow-hidden"
>
<div class="max-h-[208px] overflow-y-auto">
<div
v-if="filteredOptions.length === 0 && query !== ''"
class="relative cursor-default select-none text-center py-2"
>
No result for your search query
</div>
<ComboboxOption
v-for="item in filteredOptions"
v-slot="{ selected, disabled, active }"
:key="item.id"
:key="item.id ?? NULL_SYMBOL"
:value="item.id"
as="template"
>
Expand Down
18 changes: 12 additions & 6 deletions apps/ui/src/components/FormSpaceStrategies.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
import objectHash from 'object-hash';
import { MAX_STRATEGIES } from '@/helpers/turbo';
import { StrategyConfig, StrategyTemplate } from '@/networks/types';
import { NetworkID, Space } from '@/types';
import { ChainId, NetworkID, Space } from '@/types';
const snapshotChainId = defineModel<string>('snapshotChainId', {
const snapshotChainId = defineModel<number>('snapshotChainId', {
required: true
});
const strategies = defineModel<StrategyConfig[]>('strategies', {
Expand Down Expand Up @@ -56,7 +56,7 @@ async function handleEditStrategy(strategy: StrategyConfig) {
isEditStrategyModalOpen.value = true;
}
function handleSaveStrategy(params: Record<string, any>, network: string) {
function handleSaveStrategy(params: Record<string, any>, network: ChainId) {
const editedStrategyValue = editedStrategy.value;
if (editedStrategyValue === null) return;
Expand All @@ -79,7 +79,7 @@ function handleSaveStrategy(params: Record<string, any>, network: string) {
});
}
function validateStrategy(params: Record<string, any>, network: string) {
function validateStrategy(params: Record<string, any>, network: ChainId) {
const editedStrategyValue = editedStrategy.value;
if (editedStrategyValue === null) return;
Expand Down Expand Up @@ -111,8 +111,14 @@ watchEffect(() => {
<div class="s-box mb-4">
<UiSelectorNetwork
v-model="snapshotChainId"
:network-id="networkId"
tooltip="The default network used for this space. Networks can also be specified in individual strategies"
:definition="{
type: 'number',
title: 'Network',
tooltip:
'The default network used for this space. Networks can also be specified in individual strategies',
examples: ['Select network'],
networkId
}"
/>
</div>
<UiContainerSettings
Expand Down
28 changes: 22 additions & 6 deletions apps/ui/src/components/FormSpaceTreasuries.vue
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
<script setup lang="ts">
import Draggable from 'vuedraggable';
import { SpaceMetadataTreasury } from '@/types';
import { offchainNetworks } from '@/networks';
import { NetworkID, SpaceMetadataTreasury } from '@/types';
const model = defineModel<SpaceMetadataTreasury[]>({
required: true
});
defineProps<{
const props = defineProps<{
networkId: NetworkID;
limit?: number;
}>();
Expand All @@ -18,13 +20,26 @@ const editedTreasury = ref<number | null>(null);
const treasuryInitialState = ref<any | null>(null);
function addTreasuryConfig(config: SpaceMetadataTreasury) {
const otherTreasuries =
editedTreasury.value !== null
? model.value
.slice(0, editedTreasury.value)
.concat(model.value.slice(editedTreasury.value + 1))
: model.value;
const getId = (t: SpaceMetadataTreasury) => {
const networkIdentifeir = offchainNetworks.includes(props.networkId)
? t.chainId
: t.network;
return `${t.name}-${networkIdentifeir}-${t.address}`;
};
const existingTreasuries = new Map(
model.value.map(t => [`${t.name}-${t.network}-${t.address}`, true])
otherTreasuries.map(t => [getId(t), true])
);
if (
existingTreasuries.has(`${config.name}-${config.network}-${config.address}`)
) {
if (existingTreasuries.has(getId(config))) {
return addNotification(
'error',
'Treasury with this name and wallet already exists'
Expand Down Expand Up @@ -104,6 +119,7 @@ function deleteTreasury(index: number) {
<teleport to="#modal">
<ModalTreasuryConfig
:open="modalOpen"
:network-id="networkId"
:initial-state="treasuryInitialState ?? undefined"
@close="modalOpen = false"
@add="addTreasuryConfig"
Expand Down
17 changes: 11 additions & 6 deletions apps/ui/src/components/Modal/EditStrategy.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { clone } from '@/helpers/utils';
import { validateForm } from '@/helpers/validation';
import { getNetwork } from '@/networks';
import { NetworkID } from '@/types';
import { ChainId, NetworkID } from '@/types';
const CUSTOM_ERROR_SYMBOL = Symbol('customError');
Expand All @@ -12,11 +12,11 @@ const props = withDefaults(
networkId: NetworkID;
strategyAddress: string;
initialState?: any;
initialNetwork?: string;
initialNetwork?: ChainId;
definition?: any;
customErrorValidation?: (
value: Record<string, any>,
network: string
network: ChainId
) => string | undefined;
withNetworkSelector?: boolean;
}>(),
Expand All @@ -27,10 +27,10 @@ const props = withDefaults(
const emit = defineEmits<{
(e: 'close');
(e: 'save', value: Record<string, any>, network: string);
(e: 'save', value: Record<string, any>, network: ChainId);
}>();
const network = ref('');
const network: Ref<ChainId> = ref('');
const showPicker = ref(false);
const isDefinitionLoading = ref(false);
const pickerField: Ref<string | null> = ref(null);
Expand Down Expand Up @@ -161,7 +161,12 @@ watchEffect(() => {
<UiSelectorNetwork
v-if="withNetworkSelector"
v-model="network"
:network-id="networkId"
:definition="{
type: ['string', 'number'],
title: 'Network',
examples: ['Select network'],
networkId
}"
/>
<UiForm
v-if="definition"
Expand Down
34 changes: 14 additions & 20 deletions apps/ui/src/components/Modal/TreasuryConfig.vue
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
<script setup lang="ts">
import { clone } from '@/helpers/utils';
import { validateForm } from '@/helpers/validation';
import { enabledNetworks, getNetwork } from '@/networks';
import { SpaceMetadataTreasury } from '@/types';
import { offchainNetworks } from '@/networks';
import { NetworkID, SpaceMetadataTreasury } from '@/types';
const DEFAULT_FORM_STATE = {
name: '',
address: '',
network: null
network: null,
chainId: null
};
const props = defineProps<{
open: boolean;
networkId: NetworkID;
initialState?: SpaceMetadataTreasury;
}>();
const emit = defineEmits<{
Expand All @@ -23,17 +25,9 @@ const showPicker = ref(false);
const searchValue = ref('');
const form: Ref<SpaceMetadataTreasury> = ref(clone(DEFAULT_FORM_STATE));
const availableNetworks = enabledNetworks
.map(id => {
const { name, readOnly } = getNetwork(id);
return {
id,
name,
readOnly
};
})
.filter(network => !network.readOnly);
const networkField = computed(() =>
offchainNetworks.includes(props.networkId) ? 'chainId' : 'network'
);
const definition = computed(() => {
return {
Expand All @@ -49,14 +43,14 @@ const definition = computed(() => {
maxLength: 32,
examples: ['Treasury name']
},
network: {
type: ['string', 'null'],
enum: [null, ...availableNetworks.map(network => network.id)],
options: [{ id: null, name: 'No treasury' }, ...availableNetworks],
[networkField.value]: {
type: ['string', 'number', 'null'],
format: 'network',
networkId: props.networkId,
title: 'Treasury network',
nullable: true
},
...(form.value.network !== null
...(form.value[networkField.value] !== null
? {
address: {
type: 'string',
Expand All @@ -78,7 +72,7 @@ const formErrors = computed(() =>
const formValid = computed(() => {
return (
Object.keys(formErrors.value).length === 0 &&
form.value.network !== null &&
form.value[networkField.value] !== null &&
form.value.address !== ''
);
});
Expand Down
3 changes: 3 additions & 0 deletions apps/ui/src/components/Ui/Form.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import InputStamp from './InputStamp.vue';
import InputString from './InputString.vue';
import Select from './Select.vue';
import SelectMultiple from './SelectMultiple.vue';
import SelectorNetwork from './SelectorNetwork.vue';
import Textarea from './Textarea.vue';
const model = defineModel<any>({ required: true });
Expand Down Expand Up @@ -72,10 +73,12 @@ const getComponent = (property: {
if (property.format === 'ens-or-address') return InputAddress;
if (property.format === 'stamp') return InputStamp;
if (property.format === 'color') return InputColor;
if (property.format === 'network') return SelectorNetwork;
if (property.enum) return Select;
return InputString;
case 'number':
case 'integer':
if (property.format === 'network') return SelectorNetwork;
if (property.format === 'duration') return InputDuration;
return InputNumber;
case 'boolean':
Expand Down
Loading

0 comments on commit 136097a

Please sign in to comment.