diff --git a/.gitignore b/.gitignore index 2f52e3301..8779d88d1 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ ## General ## _legacy +*.cache ### NPM ### node_modules @@ -13,6 +14,7 @@ thesaurex ## Created by PHPUnit/Coveralls /build +phpunit.xml.bak ## Created by Laravel /node_modules diff --git a/app/Attribute.php b/app/Attribute.php index 81c3d2edb..2658d9641 100644 --- a/app/Attribute.php +++ b/app/Attribute.php @@ -29,6 +29,7 @@ class Attribute extends Model protected $casts = [ 'restrictions' => 'array', + 'metadata' => 'array', ]; public function getActivitylogOptions() : LogOptions diff --git a/app/AttributeTypes/AttributeBase.php b/app/AttributeTypes/AttributeBase.php index 41939d947..23dfcd3c0 100644 --- a/app/AttributeTypes/AttributeBase.php +++ b/app/AttributeTypes/AttributeBase.php @@ -42,6 +42,7 @@ abstract class AttributeBase "timeperiod" => TimeperiodAttribute::class, "userlist" => UserlistAttribute::class, "url" => UrlAttribute::class, + "si-unit" => SiUnitAttribute::class, ]; public static function serialized() : array { @@ -51,7 +52,7 @@ public static function serialized() : array { ]; } - public static function getTypes(array $filters = []) : array { + public static function getTypes(bool $serialized = false, array $filters = []) : array { if(count($filters) > 0) { $types = Arr::where(self::$types, function(string $attr, string $key) use($filters) { foreach($filters as $on => $value) { @@ -70,9 +71,14 @@ public static function getTypes(array $filters = []) : array { } else { $types = self::$types; } - return array_values(array_map(function(string $class) { - return $class::serialized(); - }, $types)); + + if($serialized) { + return array_values(array_map(function(string $class) { + return $class::serialized(); + }, $types)); + } else { + return $types; + } } public static function getMatchingClass(string $datatype) : bool|AttributeBase { diff --git a/app/AttributeTypes/SiUnitAttribute.php b/app/AttributeTypes/SiUnitAttribute.php new file mode 100644 index 000000000..1f2a4e5b7 --- /dev/null +++ b/app/AttributeTypes/SiUnitAttribute.php @@ -0,0 +1,75 @@ +getUnitSystems() as $unitSystem) { + $unitsArray[$unitSystem->name] = $unitSystem->toArray(); + } + + return $unitsArray; + } + + public static function fromImport(int|float|bool|string $data): mixed { + if (!is_string($data)) { + throw new InvalidDataException(__('validation.string', ['attribute' => __('dictionary.value')])); + } + + $parts = explode(';', $data); + + if (count($parts) != 2) { + $format = __('dictionary.value') . ';' . __('dictionary.unit'); + throw new InvalidDataException(__('validation.import_format', ['format' => $format])); + } + + $value = trim($parts[0]); + if (!is_numeric($value)) { + $section_1 = __('dictionary.section') . ' 1'; + throw new InvalidDataException(__('validation.numeric', ['attribute' => $section_1])); + } + + $value = floatval($value); + $unit = trim($parts[1]); + $unitFound = UnitManager::get()->findUnitByAny($unit); + + if (!isset($unitFound)) { + $section_2 = __('dictionary.section') . ' 2'; + throw new InvalidDataException(__('validation.unit', ['attribute' => $section_2])); + } + + return json_encode([ + 'value' => $value, + 'unit' => $unitFound->getSymbol(), + 'normalized' => $unitFound->normalize($value), + ]); + } + + public static function unserialize(mixed $data): mixed { + + if (isset($data["unit"])) { + $unit = UnitManager::get()->findUnitByAny($data['unit']); + } + + if (!isset($unit) || !isset($data["value"]) || !isset($data["unit"])) { + throw new InvalidDataException(__('validation.unit', ['attribute' => __('dictionary.value')])); + } + + $data['normalized'] = $unit->normalize($data['value']); + return json_encode($data); + } + + public static function serialize(mixed $data): mixed { + return json_decode($data); + } +} diff --git a/app/AttributeTypes/TableAttribute.php b/app/AttributeTypes/TableAttribute.php index f1fc53127..15fc87445 100644 --- a/app/AttributeTypes/TableAttribute.php +++ b/app/AttributeTypes/TableAttribute.php @@ -15,7 +15,7 @@ class TableAttribute extends AttributeBase public static function getSelection(Attribute $a) { $types = array_map(function(array $entry) { return $entry["datatype"]; - }, AttributeBase::getTypes(['in_table' => true, 'has_selection' => true])); + }, AttributeBase::getTypes(true, ['in_table' => true, 'has_selection' => true])); $columns = Attribute::where('parent_id', $a->id) ->whereIn('datatype', $types) diff --git a/app/AttributeTypes/Units/Call.php b/app/AttributeTypes/Units/Call.php new file mode 100644 index 000000000..0de1b8251 --- /dev/null +++ b/app/AttributeTypes/Units/Call.php @@ -0,0 +1,26 @@ +addMultiple([ + Unit::createUnit('square_inch' , 'in²', Imperial::INCH_2_M , self::DIM), + Unit::createUnit('square_feet' , 'ft²', Imperial::FOOT_2_M, self::DIM), + Unit::createUnit('square_yard' , 'yd²', Imperial::YARD_2_M, self::DIM), + Unit::createUnit('acre' , 'ac' , Imperial::ACRE_2_M2), + Unit::createUnit('square_mile' , 'mi²', Imperial::MILE_2_M, self::DIM), + ]); + } +} diff --git a/app/AttributeTypes/Units/Implementations/LengthUnits.php b/app/AttributeTypes/Units/Implementations/LengthUnits.php new file mode 100644 index 000000000..d68cccd01 --- /dev/null +++ b/app/AttributeTypes/Units/Implementations/LengthUnits.php @@ -0,0 +1,34 @@ +addMultiple([ + Unit::createUnit('inch' , 'in', Imperial::INCH_2_M), + Unit::createUnit('feet' , 'ft', Imperial::FOOT_2_M), + Unit::createUnit('yard' , 'yd', Imperial::YARD_2_M), + Unit::createUnit('mile' , 'mi', Imperial::MILE_2_M), + ]); + $this->add( + Unit::createUnit('light_year', 'ly', General::LIGHTYEAR), + ); + } +} diff --git a/app/AttributeTypes/Units/Implementations/MassUnits.php b/app/AttributeTypes/Units/Implementations/MassUnits.php new file mode 100644 index 000000000..cb9a205e4 --- /dev/null +++ b/app/AttributeTypes/Units/Implementations/MassUnits.php @@ -0,0 +1,34 @@ +addImperialUnits(); + } + + private function addImperialUnits(){ + $this->addMultiple([ + Unit::createUnit('ounce', 'oz', Imperial::OUNCE_2_G), + Unit::createUnit('pound', 'lb', Imperial::POUND_2_G), + ]); + } + + +} \ No newline at end of file diff --git a/app/AttributeTypes/Units/Implementations/TemperatureUnits.php b/app/AttributeTypes/Units/Implementations/TemperatureUnits.php new file mode 100644 index 000000000..2563b63f4 --- /dev/null +++ b/app/AttributeTypes/Units/Implementations/TemperatureUnits.php @@ -0,0 +1,19 @@ +addAllUnitSystems(); + } + + public function addAllUnitSystems(): void { + $this->addUnitSystems([ + new AreaUnits(), + new LengthUnits(), + new MassUnits(), + new TemperatureUnits(), + new TimeUnits(), + new VolumeUnits(), + ]); + } + + public function addUnitSystems(array $unitSystems): void { + foreach ($unitSystems as $unitSystem) { + $this->addUnitSystem($unitSystem); + } + } + + public function addUnitSystem(UnitSystem $system): void { + foreach ($system->getUnits() as $unit) { + $this->verifyUnitDoesNotExist($system, $unit); + } + + $this->unitSystems[] = $system; + } + + /** + * Verifies that the unit does not already exist in the unit systems. + * The unit systems are rigidly set beforehand and should be validated on startup. + * If duplicates are being added, an error is thrown. + * + * @param Unit $unit + * @throws Exception + */ + private function verifyUnitDoesNotExist(unitSystem $unitSystem, Unit $unit): void { + + $unityByLabel = $this->findUnitByLabel($unit->getLabel()); + if (isset($unityByLabel)) { + throw new Exception('Error on "' . $unitSystem->getName() . '": Unit with Label "' . $unit->getLabel() . '" already exists'); + } + + $unitBySymbol = $this->findUnitBySymbol($unit->getSymbol()); + if (isset($unitBySymbol)) { + throw new Exception('Error on "' . $unitSystem->getName() . '": Unit with Symbol "' . $unit->getSymbol() . '" already exists'); + } + } + + function findUnitByAny(string $text): ?Unit { + $unit = $this->findUnitByLabel($text); + if (isset($unit)) { + return $unit; + } + $unit = $this->findUnitBySymbol($text); + if (isset($unit)) { + return $unit; + } + + return null; + } + + function findUnitByLabel(string $label): ?Unit { + return $this->getByUnitSystemFunc($label, 'getByLabel'); + } + + function findUnitBySymbol(string $symbol): ?Unit { + return $this->getByUnitSystemFunc($symbol, 'getBySymbol'); + } + + private function getByUnitSystemFunc(string $value, string $functionName): ?Unit { + foreach ($this->unitSystems as $unitSystem) { + $unit = $unitSystem->{$functionName}($value); + if (isset($unit)) { + return $unit; + } + } + return null; + } + + public function getUnitSystem(string $name): ?UnitSystem { + foreach ($this->unitSystems as $unitSystem) { + if ($unitSystem->getName() == $name) { + return $unitSystem; + } + } + return null; + } + + public function getUnitSystems(): array { + return $this->unitSystems; + } + + public function hasQuantity(string $quantity): bool { + foreach ($this->unitSystems as $unitSystem) { + if ($unitSystem->getName() == $quantity) { + return true; + } + } + return false; + } +} diff --git a/app/AttributeTypes/Units/Implementations/VolumeUnits.php b/app/AttributeTypes/Units/Implementations/VolumeUnits.php new file mode 100644 index 000000000..20b74bce4 --- /dev/null +++ b/app/AttributeTypes/Units/Implementations/VolumeUnits.php @@ -0,0 +1,32 @@ +addImperialUnits(); + } + + private function addImperialUnits() { + $this->addMultiple([ + Unit::createUnit('fluid_ounce_us', 'fl oz', Imperial::FLUID_OUNCE_US), + Unit::createUnit('pint_us' , 'pt', Imperial::PINT_US), + Unit::createUnit('gallon_us' , 'gal', Imperial::GALLON_US), + Unit::createUnit('cubic_mile' , 'mi³', Imperial::MILE_2_M, self::DIM), + ]); + } +} diff --git a/app/AttributeTypes/Units/Unit.php b/app/AttributeTypes/Units/Unit.php new file mode 100644 index 000000000..9a3d03038 --- /dev/null +++ b/app/AttributeTypes/Units/Unit.php @@ -0,0 +1,69 @@ +label = $label; + $this->symbol = $symbol; + $this->conversion = $conversion; + $this->base = $base; + } + + public static function createBase(string $label, string $symbol) { + $conversion = Call::identity(); + return new self($label, $symbol, $conversion, true); + } + + public static function createUnit(string $label, string $symbol, float $factor, int $dimension = 1, UnitType $type = UnitType::DEFAULT) { + if($type == UnitType::SI) { + $conversion = Call::si($factor, $dimension); + } else { + $conversion = Call::multiply($factor, $dimension); + } + return new self($label, $symbol, $conversion); + } + + public static function createWith(string $label, string $symbol, callable $conversion) { + return new self($label, $symbol, $conversion); + } + + public function getLabel() { + return $this->label; + } + + public function getSymbol() { + return $this->symbol; + } + + public function getBase() { + return $this->base; + } + + // redundant?? + public function is($value) { + return $this->normalize($value); + } + + public function normalize($value) { + return ($this->conversion)($value); + } + + public function toObject() { + return [ + 'label' => $this->label, + 'symbol' => $this->symbol, + ]; + } +} diff --git a/app/AttributeTypes/Units/UnitSystem.php b/app/AttributeTypes/Units/UnitSystem.php new file mode 100644 index 000000000..201a7a9c6 --- /dev/null +++ b/app/AttributeTypes/Units/UnitSystem.php @@ -0,0 +1,97 @@ +name = $name; + $baseFound = false; + foreach($units as $unit) { + $position = $this->add($unit); + if($unit->getBase()) { + if($baseFound) { + throw new Exception('Base already defined for this Unit System'); + } + $baseFound = true; + $this->baseIndex = $position - 1; + } + } + + if(!$baseFound) { + throw new Exception('No Base defined for this Unit System'); + } + } + + public function addMultiple(array $units) { + foreach ($units as $unit) { + if($unit->getBase()) { + throw new Exception('Base already defined for this Unit System'); + } + $this->add($unit); + } + } + + public function add(Unit $unit) { + if (isset($this->labelMap[$unit->getLabel()]) || isset($this->symbolMap[$unit->getSymbol()])) { + throw new Exception("Unit already exists: {$unit->getLabel()} ({$unit->getSymbol()})"); + } + + if($unit->getBase() && isset($this->baseIndex)) { + throw new Exception('Base already defined for this Unit System'); + } + + $this->labelMap[$unit->getLabel()] = $unit; + $this->symbolMap[$unit->getSymbol()] = $unit; + $this->units[] = $unit; + return count($this->units); + } + + public function get(string $label): ?Unit { + return $this->getByLabel($label); + } + + public function getByLabel(string $label): ?Unit { + + if (!isset($this->labelMap[$label])) return null; + + return $this->labelMap[$label]; + } + + public function getBySymbol(string $symbol): ?Unit { + if (!isset($this->symbolMap[$symbol])) return null; + + return $this->symbolMap[$symbol]; + } + + public function getBaseUnit() { + return $this->units[$this->baseIndex]; + } + + public function getName() { + return $this->name; + } + + public function getUnits() { + return $this->units; + } + + public function toArray() { + return [ + 'default' => $this->getBaseUnit()->getSymbol(), // Shouldn't we better use the label here? + 'units' => array_map(function ($unit) { + return $unit->toObject(); + }, $this->units), + ]; + } +} diff --git a/app/Http/Controllers/EditorController.php b/app/Http/Controllers/EditorController.php index 7243d3277..b8cc009f0 100644 --- a/app/Http/Controllers/EditorController.php +++ b/app/Http/Controllers/EditorController.php @@ -118,7 +118,7 @@ public function getAttributeTypes() { ], 403); } - return response()->json(AttributeBase::getTypes()); + return response()->json(AttributeBase::getTypes(true)); } public function getAvailableGeometryTypes() { @@ -208,7 +208,9 @@ public function addAttribute(Request $request) { 'restricted_types' => 'nullable|array|exists:entity_types,id', 'columns' => 'required_if:datatype,table|array|min:1', 'text' => 'string', - 'recursive' => 'nullable|boolean_string' + 'recursive' => 'nullable|boolean_string', + 'si_base' => 'nullable|si_baseunit', + 'si_default' => 'nullable|si_unit:si_base' ]); $lid = $request->get('label_id'); @@ -232,6 +234,12 @@ public function addAttribute(Request $request) { if ($request->has('text')) { $attr->text = $request->get('text'); } + if($request->has('si_base')) { + $attr->metadata = [ + 'si_baseunit' => $request->get('si_base'), + 'si_default' => $request->get('si_default'), + ]; + } $attr->save(); if ($datatype == 'table') { diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index 5bf2ab8b0..8ddf94ea8 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers; +use App\AttributeTypes\AttributeBase; use App\EntityType; use App\Plugin; use App\Preference; @@ -38,6 +39,14 @@ public function getGlobalData() { $concepts = ThConcept::getMap($locale); + $datatypes = AttributeBase::getTypes(); + $datatypeData = []; + foreach($datatypes as $key => $datatype) { + if(method_exists($datatype, "getGlobalData")) { + $datatypeData[$key] = $datatype::getGlobalData(); + } + } + $entityTypes = EntityType::with(['sub_entity_types', 'layer', 'attributes']) ->orderBy('id') ->get(); @@ -48,6 +57,7 @@ public function getGlobalData() { 'preferences' => $preferenceValues, 'concepts' => $concepts, 'entityTypes' => $entityTypeMap, + 'datatype_data' => $datatypeData, 'colorsets' => sp_get_themes(), 'analysis' => sp_has_analysis(), ]); diff --git a/app/Patterns/Singleton.php b/app/Patterns/Singleton.php new file mode 100644 index 000000000..560426f95 --- /dev/null +++ b/app/Patterns/Singleton.php @@ -0,0 +1,19 @@ +getDatabasePlatform()->registerDoctrineTypeMapping('geography', 'string'); DB::getDoctrineSchemaManager()->getDatabasePlatform()->registerDoctrineTypeMapping('geometry', 'string'); // In some Proxy setups it might be necessary to enforce using the app's url as root url - if(env('APP_FORCE_URL') === true) { + if (env('APP_FORCE_URL') === true) { $rootUrl = config('app.url'); URL::forceRootUrl($rootUrl); - if(Str::startsWith($rootUrl, 'https://')) { + if (Str::startsWith($rootUrl, 'https://')) { URL::forceScheme('https'); } } Paginator::useBootstrap(); - + Relation::morphMap([ 'attribute_values' => 'App\AttributeValue' ]); - - View::composer('*', function($view) { + + View::composer('*', function ($view) { $preferences = Preference::all(); $preferenceValues = []; - foreach($preferences as $p) { + foreach ($preferences as $p) { $preferenceValues[$p->label] = Preference::decodePreference($p->label, json_decode($p->default_value)); } @@ -62,18 +61,18 @@ public function boot() return $value >= $parameters[0] && $value <= $parameters[1]; }); Validator::extend('orcid', function ($attribute, $value, $parameters, $validator) { - if(preg_match('/^\d{4}-\d{4}-\d{4}-\d{3}[0-9Xx]$/', $value, $matches) !== 1 && preg_match('/^\d{15}[0-9Xx]$/', $value, $matches) !== 1) { + if (preg_match('/^\d{4}-\d{4}-\d{4}-\d{3}[0-9Xx]$/', $value, $matches) !== 1 && preg_match('/^\d{15}[0-9Xx]$/', $value, $matches) !== 1) { return false; } $strippedValue = str_replace('-', '', $value); $total = 0; - for($i=0; $ihasQuantity($value); + }); + Validator::extend('si_unit', function ($attribute, $value, $parameters, $validator) { + if (count($parameters) != 1) { + return false; + } + $refField = request()->input($parameters[0]); + $unitSystem = UnitManager::get()->getUnitSystem($refField); + + if (isset($unitSystem)) { + $unit = UnitManager::get()->getUnitSystem($refField)->getByLabel($value); + if (isset($unit)) { + return true; + } + } + + return false; + }); } /** @@ -101,7 +119,6 @@ public function boot() * * @return void */ - public function register() - { + public function register() { } } diff --git a/composer.json b/composer.json index 136559928..6e51ba056 100644 --- a/composer.json +++ b/composer.json @@ -82,4 +82,4 @@ "sort-packages": true, "optimize-autoloader": true } -} +} \ No newline at end of file diff --git a/database/migrations/2024_04_04_135155_add_attribute_metadata.php b/database/migrations/2024_04_04_135155_add_attribute_metadata.php new file mode 100644 index 000000000..6314ea87b --- /dev/null +++ b/database/migrations/2024_04_04_135155_add_attribute_metadata.php @@ -0,0 +1,36 @@ +disableLogging(); + + Schema::table('attributes', function (Blueprint $table) { + $table->jsonb('metadata')->nullable(); + }); + + activity()->enableLogging(); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + activity()->disableLogging(); + + Schema::table('attributes', function (Blueprint $table) { + $table->dropColumn('metadata'); + }); + + activity()->enableLogging(); + } +}; \ No newline at end of file diff --git a/lang/de/dictionary.php b/lang/de/dictionary.php new file mode 100644 index 000000000..aeb0ae3fd --- /dev/null +++ b/lang/de/dictionary.php @@ -0,0 +1,17 @@ + 'Einheit', + 'value' => 'Wert', + 'section' => 'Bereich', +]; diff --git a/lang/de/validation.php b/lang/de/validation.php index ee493222d..91e65d9ff 100755 --- a/lang/de/validation.php +++ b/lang/de/validation.php @@ -54,6 +54,7 @@ 'array' => ':attribute muss größer oder gleich :value Elemente haben.', ], 'image' => ':attribute muss ein Bild sein.', + 'import_format' => 'Übergebene Daten haben das falsche Format, erwartet war: :format.', 'in' => 'Der gewählte Wert für :attribute ist ungültig.', 'in_array' => 'Der gewählte Wert für :attribute kommt nicht in :other vor.', 'integer' => ':attribute muss eine ganze Zahl sein.', @@ -108,6 +109,7 @@ ], 'string' => ':attribute muss ein String sein.', 'timezone' => ':attribute muss eine gültige Zeitzone sein.', + 'unit' => ':attribute muss eine gültige Einheit sein.', 'unique' => ':attribute ist schon vergeben.', 'uploaded' => ':attribute konnte nicht hochgeladen werden.', 'url' => ':attribute muss eine URL sein.', diff --git a/lang/en/dictionary.php b/lang/en/dictionary.php new file mode 100644 index 000000000..632dde0c0 --- /dev/null +++ b/lang/en/dictionary.php @@ -0,0 +1,17 @@ + 'unit', + 'value' => 'value', + 'section' => 'section', +]; diff --git a/lang/en/validation.php b/lang/en/validation.php index edc036dd0..bc6581e0a 100644 --- a/lang/en/validation.php +++ b/lang/en/validation.php @@ -43,6 +43,7 @@ 'file' => 'The :attribute must be a file.', 'filled' => 'The :attribute field must have a value.', 'image' => 'The :attribute must be an image.', + 'import_format' => 'Provided data has the wrong format, expected: :format', 'in' => 'The selected :attribute is invalid.', 'in_array' => 'The :attribute field does not exist in :other.', 'integer' => 'The :attribute must be an integer.', @@ -84,6 +85,7 @@ ], 'string' => 'The :attribute must be a string.', 'timezone' => 'The :attribute must be a valid zone.', + 'unit' => 'The :attribute must be a valid unit.', 'unique' => 'The :attribute has already been taken.', 'uploaded' => 'The :attribute failed to upload.', 'url' => 'The :attribute format is invalid.', diff --git a/phpunit.xml b/phpunit.xml index acd2d736b..45f2f6d9a 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,10 +1,14 @@ - - - - ./app - - + ./tests/Feature @@ -14,9 +18,22 @@ - - - - + + + + - + + + ./app + + + \ No newline at end of file diff --git a/resources/js/api.js b/resources/js/api.js index 0326968d0..f594b0e8d 100644 --- a/resources/js/api.js +++ b/resources/js/api.js @@ -145,6 +145,7 @@ export async function fetchPreData(locale) { store.commit('setSystemPreferences', response.data.system_preferences); store.dispatch('setColorSets', response.data.colorsets); store.dispatch('setAnalysis', response.data.analysis); + store.dispatch('setDatatypeData', response.data.datatype_data); if(auth.ready()) { auth.load().then(_ => { @@ -530,6 +531,10 @@ export async function addAttribute(attribute) { if(attribute.textContent) { data.text = attribute.textContent; } + if(attribute.siGroup) { + data.si_base = attribute.siGroup; + data.si_default = attribute.siGroupUnit; + } return $httpQueue.add( () => http.post(`/editor/dm/attribute`, data).then(response => response.data) diff --git a/resources/js/bootstrap/store.js b/resources/js/bootstrap/store.js index 0541c622c..74bdf1ebc 100644 --- a/resources/js/bootstrap/store.js +++ b/resources/js/bootstrap/store.js @@ -57,6 +57,7 @@ export const store = createStore({ permissions: [], preferences: {}, systemPreferences: {}, + datatypeData: {}, tags: [], roles: [], rolePresets: [], @@ -634,6 +635,11 @@ export const store = createStore({ setAnalysis(state, data) { state.hasAnalysis = data; }, + setDatatypeData(state, data) { + for(let k in data) { + state.datatypeData[k] = data[k]; + } + }, }, actions: { setAppState({commit}, data) { @@ -897,6 +903,9 @@ export const store = createStore({ setAnalysis({commit}, data) { commit('setAnalysis', data); }, + setDatatypeData({commit}, data) { + commit('setDatatypeData', data); + }, }, getters: { appInitialized: state => state.appInitialized, @@ -923,6 +932,8 @@ export const store = createStore({ preferenceByKey: state => key => state.preferences[key], preferences: state => state.preferences, systemPreferences: state => state.systemPreferences, + datatypeData: state => state.datatypeData, + datatypeDataOf: state => key => state.datatypeData[key], tags: state => state.tags, roles: state => noPerms => { return noPerms ? state.roles.map(r => { diff --git a/resources/js/components/AttributeList.vue b/resources/js/components/AttributeList.vue index 85647492b..0cc5b8557 100644 --- a/resources/js/components/AttributeList.vue +++ b/resources/js/components/AttributeList.vue @@ -198,7 +198,7 @@ :disabled="element.isDisabled || state.hiddenAttributeList[element.id] || isDisabledInModeration(element.id)" :name="`attr-${element.id}`" :value="state.attributeValues[element.id].value" - :epochs="selections[element.id]" + :epochs="selections[element.id] || []" :type="element.datatype" @change="e => updateDirtyState(e, element.id)" /> @@ -212,6 +212,16 @@ @change="e => updateDirtyState(e, element.id)" /> + + @@ -397,6 +407,7 @@ import List from '@/components/attribute/List.vue'; import Epoch from '@/components/attribute/Epoch.vue'; import Dimension from '@/components/attribute/Dimension.vue'; + import SiUnit from '@/components/attribute/SiUnit.vue'; import Tabular from '@/components/attribute/Tabular.vue'; import Iconclass from '@/components/attribute/Iconclass.vue'; import RISM from '@/components/attribute/Rism.vue'; @@ -425,6 +436,7 @@ 'percentage-attribute': Percentage, 'serial-attribute': Serial, 'dimension-attribute': Dimension, + 'si-unit-attribute': SiUnit, 'epoch-attribute': Epoch, 'list-attribute': List, 'tabular-attribute': Tabular, @@ -469,10 +481,10 @@ type: Boolean, default: false }, - group: { // required if onReorder is set // TODO + group: { required: false, - type: String, - default: null, + type: Object, + default: _ => new Object(), }, isSource: { required: false, diff --git a/resources/js/components/AttributeTemplate.vue b/resources/js/components/AttributeTemplate.vue index b18cac07f..f4f753b88 100644 --- a/resources/js/components/AttributeTemplate.vue +++ b/resources/js/components/AttributeTemplate.vue @@ -192,6 +192,82 @@ /> +