From 2b8e531054bdbdf356623ecacccf6bb8f824b1ae Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Fri, 7 Jun 2024 19:16:49 +0100 Subject: [PATCH 01/76] Working prototype of dictionaries feature MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ready for Flat Camp ⛺ --- resources/dictionaries/countries.json | 1 + resources/js/bootstrap/fieldtypes.js | 2 + .../fieldtypes/DictionaryFieldtype.vue | 179 ++++++++++++++++++ src/Dictionaries/Countries.php | 39 ++++ src/Dictionaries/Dictionary.php | 23 +++ src/Dictionaries/DictionaryRepository.php | 23 +++ src/Facades/Dictionary.php | 21 ++ src/Fieldtypes/Dictionary.php | 112 +++++++++++ src/Providers/AddonServiceProvider.php | 16 ++ src/Providers/ExtensionServiceProvider.php | 11 ++ src/Tags/Collection/Entries.php | 2 +- tests/Antlers/Runtime/ArraysTest.php | 10 +- 12 files changed, 433 insertions(+), 6 deletions(-) create mode 100644 resources/dictionaries/countries.json create mode 100644 resources/js/components/fieldtypes/DictionaryFieldtype.vue create mode 100644 src/Dictionaries/Countries.php create mode 100644 src/Dictionaries/Dictionary.php create mode 100644 src/Dictionaries/DictionaryRepository.php create mode 100644 src/Facades/Dictionary.php create mode 100644 src/Fieldtypes/Dictionary.php diff --git a/resources/dictionaries/countries.json b/resources/dictionaries/countries.json new file mode 100644 index 0000000000..41592b0d81 --- /dev/null +++ b/resources/dictionaries/countries.json @@ -0,0 +1 @@ +[{"name":"Afghanistan","iso3":"AFG","iso2":"AF","region":"Asia","subregion":"Southern Asia","emoji":"\ud83c\udde6\ud83c\uddeb"},{"name":"Aland Islands","iso3":"ALA","iso2":"AX","region":"Europe","subregion":"Northern Europe","emoji":"\ud83c\udde6\ud83c\uddfd"},{"name":"Albania","iso3":"ALB","iso2":"AL","region":"Europe","subregion":"Southern Europe","emoji":"\ud83c\udde6\ud83c\uddf1"},{"name":"Algeria","iso3":"DZA","iso2":"DZ","region":"Africa","subregion":"Northern Africa","emoji":"\ud83c\udde9\ud83c\uddff"},{"name":"American Samoa","iso3":"ASM","iso2":"AS","region":"Oceania","subregion":"Polynesia","emoji":"\ud83c\udde6\ud83c\uddf8"},{"name":"Andorra","iso3":"AND","iso2":"AD","region":"Europe","subregion":"Southern Europe","emoji":"\ud83c\udde6\ud83c\udde9"},{"name":"Angola","iso3":"AGO","iso2":"AO","region":"Africa","subregion":"Middle Africa","emoji":"\ud83c\udde6\ud83c\uddf4"},{"name":"Anguilla","iso3":"AIA","iso2":"AI","region":"Americas","subregion":"Caribbean","emoji":"\ud83c\udde6\ud83c\uddee"},{"name":"Antarctica","iso3":"ATA","iso2":"AQ","region":"Polar","subregion":"","emoji":"\ud83c\udde6\ud83c\uddf6"},{"name":"Antigua And Barbuda","iso3":"ATG","iso2":"AG","region":"Americas","subregion":"Caribbean","emoji":"\ud83c\udde6\ud83c\uddec"},{"name":"Argentina","iso3":"ARG","iso2":"AR","region":"Americas","subregion":"South America","emoji":"\ud83c\udde6\ud83c\uddf7"},{"name":"Armenia","iso3":"ARM","iso2":"AM","region":"Asia","subregion":"Western Asia","emoji":"\ud83c\udde6\ud83c\uddf2"},{"name":"Aruba","iso3":"ABW","iso2":"AW","region":"Americas","subregion":"Caribbean","emoji":"\ud83c\udde6\ud83c\uddfc"},{"name":"Australia","iso3":"AUS","iso2":"AU","region":"Oceania","subregion":"Australia and New Zealand","emoji":"\ud83c\udde6\ud83c\uddfa"},{"name":"Austria","iso3":"AUT","iso2":"AT","region":"Europe","subregion":"Western Europe","emoji":"\ud83c\udde6\ud83c\uddf9"},{"name":"Azerbaijan","iso3":"AZE","iso2":"AZ","region":"Asia","subregion":"Western Asia","emoji":"\ud83c\udde6\ud83c\uddff"},{"name":"Bahamas The","iso3":"BHS","iso2":"BS","region":"Americas","subregion":"Caribbean","emoji":"\ud83c\udde7\ud83c\uddf8"},{"name":"Bahrain","iso3":"BHR","iso2":"BH","region":"Asia","subregion":"Western Asia","emoji":"\ud83c\udde7\ud83c\udded"},{"name":"Bangladesh","iso3":"BGD","iso2":"BD","region":"Asia","subregion":"Southern Asia","emoji":"\ud83c\udde7\ud83c\udde9"},{"name":"Barbados","iso3":"BRB","iso2":"BB","region":"Americas","subregion":"Caribbean","emoji":"\ud83c\udde7\ud83c\udde7"},{"name":"Belarus","iso3":"BLR","iso2":"BY","region":"Europe","subregion":"Eastern Europe","emoji":"\ud83c\udde7\ud83c\uddfe"},{"name":"Belgium","iso3":"BEL","iso2":"BE","region":"Europe","subregion":"Western Europe","emoji":"\ud83c\udde7\ud83c\uddea"},{"name":"Belize","iso3":"BLZ","iso2":"BZ","region":"Americas","subregion":"Central America","emoji":"\ud83c\udde7\ud83c\uddff"},{"name":"Benin","iso3":"BEN","iso2":"BJ","region":"Africa","subregion":"Western Africa","emoji":"\ud83c\udde7\ud83c\uddef"},{"name":"Bermuda","iso3":"BMU","iso2":"BM","region":"Americas","subregion":"Northern America","emoji":"\ud83c\udde7\ud83c\uddf2"},{"name":"Bhutan","iso3":"BTN","iso2":"BT","region":"Asia","subregion":"Southern Asia","emoji":"\ud83c\udde7\ud83c\uddf9"},{"name":"Bolivia","iso3":"BOL","iso2":"BO","region":"Americas","subregion":"South America","emoji":"\ud83c\udde7\ud83c\uddf4"},{"name":"Bonaire, Sint Eustatius and Saba","iso3":"BES","iso2":"BQ","region":"Americas","subregion":"Caribbean","emoji":"\ud83c\udde7\ud83c\uddf6"},{"name":"Bosnia and Herzegovina","iso3":"BIH","iso2":"BA","region":"Europe","subregion":"Southern Europe","emoji":"\ud83c\udde7\ud83c\udde6"},{"name":"Botswana","iso3":"BWA","iso2":"BW","region":"Africa","subregion":"Southern Africa","emoji":"\ud83c\udde7\ud83c\uddfc"},{"name":"Bouvet Island","iso3":"BVT","iso2":"BV","region":"","subregion":"","emoji":"\ud83c\udde7\ud83c\uddfb"},{"name":"Brazil","iso3":"BRA","iso2":"BR","region":"Americas","subregion":"South America","emoji":"\ud83c\udde7\ud83c\uddf7"},{"name":"British Indian Ocean Territory","iso3":"IOT","iso2":"IO","region":"Africa","subregion":"Eastern Africa","emoji":"\ud83c\uddee\ud83c\uddf4"},{"name":"Brunei","iso3":"BRN","iso2":"BN","region":"Asia","subregion":"South-Eastern Asia","emoji":"\ud83c\udde7\ud83c\uddf3"},{"name":"Bulgaria","iso3":"BGR","iso2":"BG","region":"Europe","subregion":"Eastern Europe","emoji":"\ud83c\udde7\ud83c\uddec"},{"name":"Burkina Faso","iso3":"BFA","iso2":"BF","region":"Africa","subregion":"Western Africa","emoji":"\ud83c\udde7\ud83c\uddeb"},{"name":"Burundi","iso3":"BDI","iso2":"BI","region":"Africa","subregion":"Eastern Africa","emoji":"\ud83c\udde7\ud83c\uddee"},{"name":"Cambodia","iso3":"KHM","iso2":"KH","region":"Asia","subregion":"South-Eastern Asia","emoji":"\ud83c\uddf0\ud83c\udded"},{"name":"Cameroon","iso3":"CMR","iso2":"CM","region":"Africa","subregion":"Middle Africa","emoji":"\ud83c\udde8\ud83c\uddf2"},{"name":"Canada","iso3":"CAN","iso2":"CA","region":"Americas","subregion":"Northern America","emoji":"\ud83c\udde8\ud83c\udde6"},{"name":"Cape Verde","iso3":"CPV","iso2":"CV","region":"Africa","subregion":"Western Africa","emoji":"\ud83c\udde8\ud83c\uddfb"},{"name":"Cayman Islands","iso3":"CYM","iso2":"KY","region":"Americas","subregion":"Caribbean","emoji":"\ud83c\uddf0\ud83c\uddfe"},{"name":"Central African Republic","iso3":"CAF","iso2":"CF","region":"Africa","subregion":"Middle Africa","emoji":"\ud83c\udde8\ud83c\uddeb"},{"name":"Chad","iso3":"TCD","iso2":"TD","region":"Africa","subregion":"Middle Africa","emoji":"\ud83c\uddf9\ud83c\udde9"},{"name":"Chile","iso3":"CHL","iso2":"CL","region":"Americas","subregion":"South America","emoji":"\ud83c\udde8\ud83c\uddf1"},{"name":"China","iso3":"CHN","iso2":"CN","region":"Asia","subregion":"Eastern Asia","emoji":"\ud83c\udde8\ud83c\uddf3"},{"name":"Christmas Island","iso3":"CXR","iso2":"CX","region":"Oceania","subregion":"Australia and New Zealand","emoji":"\ud83c\udde8\ud83c\uddfd"},{"name":"Cocos (Keeling) Islands","iso3":"CCK","iso2":"CC","region":"Oceania","subregion":"Australia and New Zealand","emoji":"\ud83c\udde8\ud83c\udde8"},{"name":"Colombia","iso3":"COL","iso2":"CO","region":"Americas","subregion":"South America","emoji":"\ud83c\udde8\ud83c\uddf4"},{"name":"Comoros","iso3":"COM","iso2":"KM","region":"Africa","subregion":"Eastern Africa","emoji":"\ud83c\uddf0\ud83c\uddf2"},{"name":"Congo","iso3":"COG","iso2":"CG","region":"Africa","subregion":"Middle Africa","emoji":"\ud83c\udde8\ud83c\uddec"},{"name":"Cook Islands","iso3":"COK","iso2":"CK","region":"Oceania","subregion":"Polynesia","emoji":"\ud83c\udde8\ud83c\uddf0"},{"name":"Costa Rica","iso3":"CRI","iso2":"CR","region":"Americas","subregion":"Central America","emoji":"\ud83c\udde8\ud83c\uddf7"},{"name":"Cote D'Ivoire (Ivory Coast)","iso3":"CIV","iso2":"CI","region":"Africa","subregion":"Western Africa","emoji":"\ud83c\udde8\ud83c\uddee"},{"name":"Croatia","iso3":"HRV","iso2":"HR","region":"Europe","subregion":"Southern Europe","emoji":"\ud83c\udded\ud83c\uddf7"},{"name":"Cuba","iso3":"CUB","iso2":"CU","region":"Americas","subregion":"Caribbean","emoji":"\ud83c\udde8\ud83c\uddfa"},{"name":"Cura\u00e7ao","iso3":"CUW","iso2":"CW","region":"Americas","subregion":"Caribbean","emoji":"\ud83c\udde8\ud83c\uddfc"},{"name":"Cyprus","iso3":"CYP","iso2":"CY","region":"Europe","subregion":"Southern Europe","emoji":"\ud83c\udde8\ud83c\uddfe"},{"name":"Czech Republic","iso3":"CZE","iso2":"CZ","region":"Europe","subregion":"Eastern Europe","emoji":"\ud83c\udde8\ud83c\uddff"},{"name":"Democratic Republic of the Congo","iso3":"COD","iso2":"CD","region":"Africa","subregion":"Middle Africa","emoji":"\ud83c\udde8\ud83c\udde9"},{"name":"Denmark","iso3":"DNK","iso2":"DK","region":"Europe","subregion":"Northern Europe","emoji":"\ud83c\udde9\ud83c\uddf0"},{"name":"Djibouti","iso3":"DJI","iso2":"DJ","region":"Africa","subregion":"Eastern Africa","emoji":"\ud83c\udde9\ud83c\uddef"},{"name":"Dominica","iso3":"DMA","iso2":"DM","region":"Americas","subregion":"Caribbean","emoji":"\ud83c\udde9\ud83c\uddf2"},{"name":"Dominican Republic","iso3":"DOM","iso2":"DO","region":"Americas","subregion":"Caribbean","emoji":"\ud83c\udde9\ud83c\uddf4"},{"name":"East Timor","iso3":"TLS","iso2":"TL","region":"Asia","subregion":"South-Eastern Asia","emoji":"\ud83c\uddf9\ud83c\uddf1"},{"name":"Ecuador","iso3":"ECU","iso2":"EC","region":"Americas","subregion":"South America","emoji":"\ud83c\uddea\ud83c\udde8"},{"name":"Egypt","iso3":"EGY","iso2":"EG","region":"Africa","subregion":"Northern Africa","emoji":"\ud83c\uddea\ud83c\uddec"},{"name":"El Salvador","iso3":"SLV","iso2":"SV","region":"Americas","subregion":"Central America","emoji":"\ud83c\uddf8\ud83c\uddfb"},{"name":"Equatorial Guinea","iso3":"GNQ","iso2":"GQ","region":"Africa","subregion":"Middle Africa","emoji":"\ud83c\uddec\ud83c\uddf6"},{"name":"Eritrea","iso3":"ERI","iso2":"ER","region":"Africa","subregion":"Eastern Africa","emoji":"\ud83c\uddea\ud83c\uddf7"},{"name":"Estonia","iso3":"EST","iso2":"EE","region":"Europe","subregion":"Northern Europe","emoji":"\ud83c\uddea\ud83c\uddea"},{"name":"Ethiopia","iso3":"ETH","iso2":"ET","region":"Africa","subregion":"Eastern Africa","emoji":"\ud83c\uddea\ud83c\uddf9"},{"name":"Falkland Islands","iso3":"FLK","iso2":"FK","region":"Americas","subregion":"South America","emoji":"\ud83c\uddeb\ud83c\uddf0"},{"name":"Faroe Islands","iso3":"FRO","iso2":"FO","region":"Europe","subregion":"Northern Europe","emoji":"\ud83c\uddeb\ud83c\uddf4"},{"name":"Fiji Islands","iso3":"FJI","iso2":"FJ","region":"Oceania","subregion":"Melanesia","emoji":"\ud83c\uddeb\ud83c\uddef"},{"name":"Finland","iso3":"FIN","iso2":"FI","region":"Europe","subregion":"Northern Europe","emoji":"\ud83c\uddeb\ud83c\uddee"},{"name":"France","iso3":"FRA","iso2":"FR","region":"Europe","subregion":"Western Europe","emoji":"\ud83c\uddeb\ud83c\uddf7"},{"name":"French Guiana","iso3":"GUF","iso2":"GF","region":"Americas","subregion":"South America","emoji":"\ud83c\uddec\ud83c\uddeb"},{"name":"French Polynesia","iso3":"PYF","iso2":"PF","region":"Oceania","subregion":"Polynesia","emoji":"\ud83c\uddf5\ud83c\uddeb"},{"name":"French Southern Territories","iso3":"ATF","iso2":"TF","region":"Africa","subregion":"Southern Africa","emoji":"\ud83c\uddf9\ud83c\uddeb"},{"name":"Gabon","iso3":"GAB","iso2":"GA","region":"Africa","subregion":"Middle Africa","emoji":"\ud83c\uddec\ud83c\udde6"},{"name":"Gambia The","iso3":"GMB","iso2":"GM","region":"Africa","subregion":"Western Africa","emoji":"\ud83c\uddec\ud83c\uddf2"},{"name":"Georgia","iso3":"GEO","iso2":"GE","region":"Asia","subregion":"Western Asia","emoji":"\ud83c\uddec\ud83c\uddea"},{"name":"Germany","iso3":"DEU","iso2":"DE","region":"Europe","subregion":"Western Europe","emoji":"\ud83c\udde9\ud83c\uddea"},{"name":"Ghana","iso3":"GHA","iso2":"GH","region":"Africa","subregion":"Western Africa","emoji":"\ud83c\uddec\ud83c\udded"},{"name":"Gibraltar","iso3":"GIB","iso2":"GI","region":"Europe","subregion":"Southern Europe","emoji":"\ud83c\uddec\ud83c\uddee"},{"name":"Greece","iso3":"GRC","iso2":"GR","region":"Europe","subregion":"Southern Europe","emoji":"\ud83c\uddec\ud83c\uddf7"},{"name":"Greenland","iso3":"GRL","iso2":"GL","region":"Americas","subregion":"Northern America","emoji":"\ud83c\uddec\ud83c\uddf1"},{"name":"Grenada","iso3":"GRD","iso2":"GD","region":"Americas","subregion":"Caribbean","emoji":"\ud83c\uddec\ud83c\udde9"},{"name":"Guadeloupe","iso3":"GLP","iso2":"GP","region":"Americas","subregion":"Caribbean","emoji":"\ud83c\uddec\ud83c\uddf5"},{"name":"Guam","iso3":"GUM","iso2":"GU","region":"Oceania","subregion":"Micronesia","emoji":"\ud83c\uddec\ud83c\uddfa"},{"name":"Guatemala","iso3":"GTM","iso2":"GT","region":"Americas","subregion":"Central America","emoji":"\ud83c\uddec\ud83c\uddf9"},{"name":"Guernsey and Alderney","iso3":"GGY","iso2":"GG","region":"Europe","subregion":"Northern Europe","emoji":"\ud83c\uddec\ud83c\uddec"},{"name":"Guinea","iso3":"GIN","iso2":"GN","region":"Africa","subregion":"Western Africa","emoji":"\ud83c\uddec\ud83c\uddf3"},{"name":"Guinea-Bissau","iso3":"GNB","iso2":"GW","region":"Africa","subregion":"Western Africa","emoji":"\ud83c\uddec\ud83c\uddfc"},{"name":"Guyana","iso3":"GUY","iso2":"GY","region":"Americas","subregion":"South America","emoji":"\ud83c\uddec\ud83c\uddfe"},{"name":"Haiti","iso3":"HTI","iso2":"HT","region":"Americas","subregion":"Caribbean","emoji":"\ud83c\udded\ud83c\uddf9"},{"name":"Heard Island and McDonald Islands","iso3":"HMD","iso2":"HM","region":"","subregion":"","emoji":"\ud83c\udded\ud83c\uddf2"},{"name":"Honduras","iso3":"HND","iso2":"HN","region":"Americas","subregion":"Central America","emoji":"\ud83c\udded\ud83c\uddf3"},{"name":"Hong Kong S.A.R.","iso3":"HKG","iso2":"HK","region":"Asia","subregion":"Eastern Asia","emoji":"\ud83c\udded\ud83c\uddf0"},{"name":"Hungary","iso3":"HUN","iso2":"HU","region":"Europe","subregion":"Eastern Europe","emoji":"\ud83c\udded\ud83c\uddfa"},{"name":"Iceland","iso3":"ISL","iso2":"IS","region":"Europe","subregion":"Northern Europe","emoji":"\ud83c\uddee\ud83c\uddf8"},{"name":"India","iso3":"IND","iso2":"IN","region":"Asia","subregion":"Southern Asia","emoji":"\ud83c\uddee\ud83c\uddf3"},{"name":"Indonesia","iso3":"IDN","iso2":"ID","region":"Asia","subregion":"South-Eastern Asia","emoji":"\ud83c\uddee\ud83c\udde9"},{"name":"Iran","iso3":"IRN","iso2":"IR","region":"Asia","subregion":"Southern Asia","emoji":"\ud83c\uddee\ud83c\uddf7"},{"name":"Iraq","iso3":"IRQ","iso2":"IQ","region":"Asia","subregion":"Western Asia","emoji":"\ud83c\uddee\ud83c\uddf6"},{"name":"Ireland","iso3":"IRL","iso2":"IE","region":"Europe","subregion":"Northern Europe","emoji":"\ud83c\uddee\ud83c\uddea"},{"name":"Israel","iso3":"ISR","iso2":"IL","region":"Asia","subregion":"Western Asia","emoji":"\ud83c\uddee\ud83c\uddf1"},{"name":"Italy","iso3":"ITA","iso2":"IT","region":"Europe","subregion":"Southern Europe","emoji":"\ud83c\uddee\ud83c\uddf9"},{"name":"Jamaica","iso3":"JAM","iso2":"JM","region":"Americas","subregion":"Caribbean","emoji":"\ud83c\uddef\ud83c\uddf2"},{"name":"Japan","iso3":"JPN","iso2":"JP","region":"Asia","subregion":"Eastern Asia","emoji":"\ud83c\uddef\ud83c\uddf5"},{"name":"Jersey","iso3":"JEY","iso2":"JE","region":"Europe","subregion":"Northern Europe","emoji":"\ud83c\uddef\ud83c\uddea"},{"name":"Jordan","iso3":"JOR","iso2":"JO","region":"Asia","subregion":"Western Asia","emoji":"\ud83c\uddef\ud83c\uddf4"},{"name":"Kazakhstan","iso3":"KAZ","iso2":"KZ","region":"Asia","subregion":"Central Asia","emoji":"\ud83c\uddf0\ud83c\uddff"},{"name":"Kenya","iso3":"KEN","iso2":"KE","region":"Africa","subregion":"Eastern Africa","emoji":"\ud83c\uddf0\ud83c\uddea"},{"name":"Kiribati","iso3":"KIR","iso2":"KI","region":"Oceania","subregion":"Micronesia","emoji":"\ud83c\uddf0\ud83c\uddee"},{"name":"Kosovo","iso3":"XKX","iso2":"XK","region":"Europe","subregion":"Eastern Europe","emoji":"\ud83c\uddfd\ud83c\uddf0"},{"name":"Kuwait","iso3":"KWT","iso2":"KW","region":"Asia","subregion":"Western Asia","emoji":"\ud83c\uddf0\ud83c\uddfc"},{"name":"Kyrgyzstan","iso3":"KGZ","iso2":"KG","region":"Asia","subregion":"Central Asia","emoji":"\ud83c\uddf0\ud83c\uddec"},{"name":"Laos","iso3":"LAO","iso2":"LA","region":"Asia","subregion":"South-Eastern Asia","emoji":"\ud83c\uddf1\ud83c\udde6"},{"name":"Latvia","iso3":"LVA","iso2":"LV","region":"Europe","subregion":"Northern Europe","emoji":"\ud83c\uddf1\ud83c\uddfb"},{"name":"Lebanon","iso3":"LBN","iso2":"LB","region":"Asia","subregion":"Western Asia","emoji":"\ud83c\uddf1\ud83c\udde7"},{"name":"Lesotho","iso3":"LSO","iso2":"LS","region":"Africa","subregion":"Southern Africa","emoji":"\ud83c\uddf1\ud83c\uddf8"},{"name":"Liberia","iso3":"LBR","iso2":"LR","region":"Africa","subregion":"Western Africa","emoji":"\ud83c\uddf1\ud83c\uddf7"},{"name":"Libya","iso3":"LBY","iso2":"LY","region":"Africa","subregion":"Northern Africa","emoji":"\ud83c\uddf1\ud83c\uddfe"},{"name":"Liechtenstein","iso3":"LIE","iso2":"LI","region":"Europe","subregion":"Western Europe","emoji":"\ud83c\uddf1\ud83c\uddee"},{"name":"Lithuania","iso3":"LTU","iso2":"LT","region":"Europe","subregion":"Northern Europe","emoji":"\ud83c\uddf1\ud83c\uddf9"},{"name":"Luxembourg","iso3":"LUX","iso2":"LU","region":"Europe","subregion":"Western Europe","emoji":"\ud83c\uddf1\ud83c\uddfa"},{"name":"Macau S.A.R.","iso3":"MAC","iso2":"MO","region":"Asia","subregion":"Eastern Asia","emoji":"\ud83c\uddf2\ud83c\uddf4"},{"name":"Macedonia","iso3":"MKD","iso2":"MK","region":"Europe","subregion":"Southern Europe","emoji":"\ud83c\uddf2\ud83c\uddf0"},{"name":"Madagascar","iso3":"MDG","iso2":"MG","region":"Africa","subregion":"Eastern Africa","emoji":"\ud83c\uddf2\ud83c\uddec"},{"name":"Malawi","iso3":"MWI","iso2":"MW","region":"Africa","subregion":"Eastern Africa","emoji":"\ud83c\uddf2\ud83c\uddfc"},{"name":"Malaysia","iso3":"MYS","iso2":"MY","region":"Asia","subregion":"South-Eastern Asia","emoji":"\ud83c\uddf2\ud83c\uddfe"},{"name":"Maldives","iso3":"MDV","iso2":"MV","region":"Asia","subregion":"Southern Asia","emoji":"\ud83c\uddf2\ud83c\uddfb"},{"name":"Mali","iso3":"MLI","iso2":"ML","region":"Africa","subregion":"Western Africa","emoji":"\ud83c\uddf2\ud83c\uddf1"},{"name":"Malta","iso3":"MLT","iso2":"MT","region":"Europe","subregion":"Southern Europe","emoji":"\ud83c\uddf2\ud83c\uddf9"},{"name":"Man (Isle of)","iso3":"IMN","iso2":"IM","region":"Europe","subregion":"Northern Europe","emoji":"\ud83c\uddee\ud83c\uddf2"},{"name":"Marshall Islands","iso3":"MHL","iso2":"MH","region":"Oceania","subregion":"Micronesia","emoji":"\ud83c\uddf2\ud83c\udded"},{"name":"Martinique","iso3":"MTQ","iso2":"MQ","region":"Americas","subregion":"Caribbean","emoji":"\ud83c\uddf2\ud83c\uddf6"},{"name":"Mauritania","iso3":"MRT","iso2":"MR","region":"Africa","subregion":"Western Africa","emoji":"\ud83c\uddf2\ud83c\uddf7"},{"name":"Mauritius","iso3":"MUS","iso2":"MU","region":"Africa","subregion":"Eastern Africa","emoji":"\ud83c\uddf2\ud83c\uddfa"},{"name":"Mayotte","iso3":"MYT","iso2":"YT","region":"Africa","subregion":"Eastern Africa","emoji":"\ud83c\uddfe\ud83c\uddf9"},{"name":"Mexico","iso3":"MEX","iso2":"MX","region":"Americas","subregion":"Central America","emoji":"\ud83c\uddf2\ud83c\uddfd"},{"name":"Micronesia","iso3":"FSM","iso2":"FM","region":"Oceania","subregion":"Micronesia","emoji":"\ud83c\uddeb\ud83c\uddf2"},{"name":"Moldova","iso3":"MDA","iso2":"MD","region":"Europe","subregion":"Eastern Europe","emoji":"\ud83c\uddf2\ud83c\udde9"},{"name":"Monaco","iso3":"MCO","iso2":"MC","region":"Europe","subregion":"Western Europe","emoji":"\ud83c\uddf2\ud83c\udde8"},{"name":"Mongolia","iso3":"MNG","iso2":"MN","region":"Asia","subregion":"Eastern Asia","emoji":"\ud83c\uddf2\ud83c\uddf3"},{"name":"Montenegro","iso3":"MNE","iso2":"ME","region":"Europe","subregion":"Southern Europe","emoji":"\ud83c\uddf2\ud83c\uddea"},{"name":"Montserrat","iso3":"MSR","iso2":"MS","region":"Americas","subregion":"Caribbean","emoji":"\ud83c\uddf2\ud83c\uddf8"},{"name":"Morocco","iso3":"MAR","iso2":"MA","region":"Africa","subregion":"Northern Africa","emoji":"\ud83c\uddf2\ud83c\udde6"},{"name":"Mozambique","iso3":"MOZ","iso2":"MZ","region":"Africa","subregion":"Eastern Africa","emoji":"\ud83c\uddf2\ud83c\uddff"},{"name":"Myanmar","iso3":"MMR","iso2":"MM","region":"Asia","subregion":"South-Eastern Asia","emoji":"\ud83c\uddf2\ud83c\uddf2"},{"name":"Namibia","iso3":"NAM","iso2":"NA","region":"Africa","subregion":"Southern Africa","emoji":"\ud83c\uddf3\ud83c\udde6"},{"name":"Nauru","iso3":"NRU","iso2":"NR","region":"Oceania","subregion":"Micronesia","emoji":"\ud83c\uddf3\ud83c\uddf7"},{"name":"Nepal","iso3":"NPL","iso2":"NP","region":"Asia","subregion":"Southern Asia","emoji":"\ud83c\uddf3\ud83c\uddf5"},{"name":"Netherlands","iso3":"NLD","iso2":"NL","region":"Europe","subregion":"Western Europe","emoji":"\ud83c\uddf3\ud83c\uddf1"},{"name":"New Caledonia","iso3":"NCL","iso2":"NC","region":"Oceania","subregion":"Melanesia","emoji":"\ud83c\uddf3\ud83c\udde8"},{"name":"New Zealand","iso3":"NZL","iso2":"NZ","region":"Oceania","subregion":"Australia and New Zealand","emoji":"\ud83c\uddf3\ud83c\uddff"},{"name":"Nicaragua","iso3":"NIC","iso2":"NI","region":"Americas","subregion":"Central America","emoji":"\ud83c\uddf3\ud83c\uddee"},{"name":"Niger","iso3":"NER","iso2":"NE","region":"Africa","subregion":"Western Africa","emoji":"\ud83c\uddf3\ud83c\uddea"},{"name":"Nigeria","iso3":"NGA","iso2":"NG","region":"Africa","subregion":"Western Africa","emoji":"\ud83c\uddf3\ud83c\uddec"},{"name":"Niue","iso3":"NIU","iso2":"NU","region":"Oceania","subregion":"Polynesia","emoji":"\ud83c\uddf3\ud83c\uddfa"},{"name":"Norfolk Island","iso3":"NFK","iso2":"NF","region":"Oceania","subregion":"Australia and New Zealand","emoji":"\ud83c\uddf3\ud83c\uddeb"},{"name":"North Korea","iso3":"PRK","iso2":"KP","region":"Asia","subregion":"Eastern Asia","emoji":"\ud83c\uddf0\ud83c\uddf5"},{"name":"Northern Mariana Islands","iso3":"MNP","iso2":"MP","region":"Oceania","subregion":"Micronesia","emoji":"\ud83c\uddf2\ud83c\uddf5"},{"name":"Norway","iso3":"NOR","iso2":"NO","region":"Europe","subregion":"Northern Europe","emoji":"\ud83c\uddf3\ud83c\uddf4"},{"name":"Oman","iso3":"OMN","iso2":"OM","region":"Asia","subregion":"Western Asia","emoji":"\ud83c\uddf4\ud83c\uddf2"},{"name":"Pakistan","iso3":"PAK","iso2":"PK","region":"Asia","subregion":"Southern Asia","emoji":"\ud83c\uddf5\ud83c\uddf0"},{"name":"Palau","iso3":"PLW","iso2":"PW","region":"Oceania","subregion":"Micronesia","emoji":"\ud83c\uddf5\ud83c\uddfc"},{"name":"Palestinian Territory Occupied","iso3":"PSE","iso2":"PS","region":"Asia","subregion":"Western Asia","emoji":"\ud83c\uddf5\ud83c\uddf8"},{"name":"Panama","iso3":"PAN","iso2":"PA","region":"Americas","subregion":"Central America","emoji":"\ud83c\uddf5\ud83c\udde6"},{"name":"Papua new Guinea","iso3":"PNG","iso2":"PG","region":"Oceania","subregion":"Melanesia","emoji":"\ud83c\uddf5\ud83c\uddec"},{"name":"Paraguay","iso3":"PRY","iso2":"PY","region":"Americas","subregion":"South America","emoji":"\ud83c\uddf5\ud83c\uddfe"},{"name":"Peru","iso3":"PER","iso2":"PE","region":"Americas","subregion":"South America","emoji":"\ud83c\uddf5\ud83c\uddea"},{"name":"Philippines","iso3":"PHL","iso2":"PH","region":"Asia","subregion":"South-Eastern Asia","emoji":"\ud83c\uddf5\ud83c\udded"},{"name":"Pitcairn Island","iso3":"PCN","iso2":"PN","region":"Oceania","subregion":"Polynesia","emoji":"\ud83c\uddf5\ud83c\uddf3"},{"name":"Poland","iso3":"POL","iso2":"PL","region":"Europe","subregion":"Eastern Europe","emoji":"\ud83c\uddf5\ud83c\uddf1"},{"name":"Portugal","iso3":"PRT","iso2":"PT","region":"Europe","subregion":"Southern Europe","emoji":"\ud83c\uddf5\ud83c\uddf9"},{"name":"Puerto Rico","iso3":"PRI","iso2":"PR","region":"Americas","subregion":"Caribbean","emoji":"\ud83c\uddf5\ud83c\uddf7"},{"name":"Qatar","iso3":"QAT","iso2":"QA","region":"Asia","subregion":"Western Asia","emoji":"\ud83c\uddf6\ud83c\udde6"},{"name":"Reunion","iso3":"REU","iso2":"RE","region":"Africa","subregion":"Eastern Africa","emoji":"\ud83c\uddf7\ud83c\uddea"},{"name":"Romania","iso3":"ROU","iso2":"RO","region":"Europe","subregion":"Eastern Europe","emoji":"\ud83c\uddf7\ud83c\uddf4"},{"name":"Russia","iso3":"RUS","iso2":"RU","region":"Europe","subregion":"Eastern Europe","emoji":"\ud83c\uddf7\ud83c\uddfa"},{"name":"Rwanda","iso3":"RWA","iso2":"RW","region":"Africa","subregion":"Eastern Africa","emoji":"\ud83c\uddf7\ud83c\uddfc"},{"name":"Saint Helena","iso3":"SHN","iso2":"SH","region":"Africa","subregion":"Western Africa","emoji":"\ud83c\uddf8\ud83c\udded"},{"name":"Saint Kitts And Nevis","iso3":"KNA","iso2":"KN","region":"Americas","subregion":"Caribbean","emoji":"\ud83c\uddf0\ud83c\uddf3"},{"name":"Saint Lucia","iso3":"LCA","iso2":"LC","region":"Americas","subregion":"Caribbean","emoji":"\ud83c\uddf1\ud83c\udde8"},{"name":"Saint Pierre and Miquelon","iso3":"SPM","iso2":"PM","region":"Americas","subregion":"Northern America","emoji":"\ud83c\uddf5\ud83c\uddf2"},{"name":"Saint Vincent And The Grenadines","iso3":"VCT","iso2":"VC","region":"Americas","subregion":"Caribbean","emoji":"\ud83c\uddfb\ud83c\udde8"},{"name":"Saint-Barthelemy","iso3":"BLM","iso2":"BL","region":"Americas","subregion":"Caribbean","emoji":"\ud83c\udde7\ud83c\uddf1"},{"name":"Saint-Martin (French part)","iso3":"MAF","iso2":"MF","region":"Americas","subregion":"Caribbean","emoji":"\ud83c\uddf2\ud83c\uddeb"},{"name":"Samoa","iso3":"WSM","iso2":"WS","region":"Oceania","subregion":"Polynesia","emoji":"\ud83c\uddfc\ud83c\uddf8"},{"name":"San Marino","iso3":"SMR","iso2":"SM","region":"Europe","subregion":"Southern Europe","emoji":"\ud83c\uddf8\ud83c\uddf2"},{"name":"Sao Tome and Principe","iso3":"STP","iso2":"ST","region":"Africa","subregion":"Middle Africa","emoji":"\ud83c\uddf8\ud83c\uddf9"},{"name":"Saudi Arabia","iso3":"SAU","iso2":"SA","region":"Asia","subregion":"Western Asia","emoji":"\ud83c\uddf8\ud83c\udde6"},{"name":"Senegal","iso3":"SEN","iso2":"SN","region":"Africa","subregion":"Western Africa","emoji":"\ud83c\uddf8\ud83c\uddf3"},{"name":"Serbia","iso3":"SRB","iso2":"RS","region":"Europe","subregion":"Southern Europe","emoji":"\ud83c\uddf7\ud83c\uddf8"},{"name":"Seychelles","iso3":"SYC","iso2":"SC","region":"Africa","subregion":"Eastern Africa","emoji":"\ud83c\uddf8\ud83c\udde8"},{"name":"Sierra Leone","iso3":"SLE","iso2":"SL","region":"Africa","subregion":"Western Africa","emoji":"\ud83c\uddf8\ud83c\uddf1"},{"name":"Singapore","iso3":"SGP","iso2":"SG","region":"Asia","subregion":"South-Eastern Asia","emoji":"\ud83c\uddf8\ud83c\uddec"},{"name":"Sint Maarten (Dutch part)","iso3":"SXM","iso2":"SX","region":"Americas","subregion":"Caribbean","emoji":"\ud83c\uddf8\ud83c\uddfd"},{"name":"Slovakia","iso3":"SVK","iso2":"SK","region":"Europe","subregion":"Eastern Europe","emoji":"\ud83c\uddf8\ud83c\uddf0"},{"name":"Slovenia","iso3":"SVN","iso2":"SI","region":"Europe","subregion":"Southern Europe","emoji":"\ud83c\uddf8\ud83c\uddee"},{"name":"Solomon Islands","iso3":"SLB","iso2":"SB","region":"Oceania","subregion":"Melanesia","emoji":"\ud83c\uddf8\ud83c\udde7"},{"name":"Somalia","iso3":"SOM","iso2":"SO","region":"Africa","subregion":"Eastern Africa","emoji":"\ud83c\uddf8\ud83c\uddf4"},{"name":"South Africa","iso3":"ZAF","iso2":"ZA","region":"Africa","subregion":"Southern Africa","emoji":"\ud83c\uddff\ud83c\udde6"},{"name":"South Georgia","iso3":"SGS","iso2":"GS","region":"Americas","subregion":"South America","emoji":"\ud83c\uddec\ud83c\uddf8"},{"name":"South Korea","iso3":"KOR","iso2":"KR","region":"Asia","subregion":"Eastern Asia","emoji":"\ud83c\uddf0\ud83c\uddf7"},{"name":"South Sudan","iso3":"SSD","iso2":"SS","region":"Africa","subregion":"Middle Africa","emoji":"\ud83c\uddf8\ud83c\uddf8"},{"name":"Spain","iso3":"ESP","iso2":"ES","region":"Europe","subregion":"Southern Europe","emoji":"\ud83c\uddea\ud83c\uddf8"},{"name":"Sri Lanka","iso3":"LKA","iso2":"LK","region":"Asia","subregion":"Southern Asia","emoji":"\ud83c\uddf1\ud83c\uddf0"},{"name":"Sudan","iso3":"SDN","iso2":"SD","region":"Africa","subregion":"Northern Africa","emoji":"\ud83c\uddf8\ud83c\udde9"},{"name":"Suriname","iso3":"SUR","iso2":"SR","region":"Americas","subregion":"South America","emoji":"\ud83c\uddf8\ud83c\uddf7"},{"name":"Svalbard And Jan Mayen Islands","iso3":"SJM","iso2":"SJ","region":"Europe","subregion":"Northern Europe","emoji":"\ud83c\uddf8\ud83c\uddef"},{"name":"Swaziland","iso3":"SWZ","iso2":"SZ","region":"Africa","subregion":"Southern Africa","emoji":"\ud83c\uddf8\ud83c\uddff"},{"name":"Sweden","iso3":"SWE","iso2":"SE","region":"Europe","subregion":"Northern Europe","emoji":"\ud83c\uddf8\ud83c\uddea"},{"name":"Switzerland","iso3":"CHE","iso2":"CH","region":"Europe","subregion":"Western Europe","emoji":"\ud83c\udde8\ud83c\udded"},{"name":"Syria","iso3":"SYR","iso2":"SY","region":"Asia","subregion":"Western Asia","emoji":"\ud83c\uddf8\ud83c\uddfe"},{"name":"Taiwan","iso3":"TWN","iso2":"TW","region":"Asia","subregion":"Eastern Asia","emoji":"\ud83c\uddf9\ud83c\uddfc"},{"name":"Tajikistan","iso3":"TJK","iso2":"TJ","region":"Asia","subregion":"Central Asia","emoji":"\ud83c\uddf9\ud83c\uddef"},{"name":"Tanzania","iso3":"TZA","iso2":"TZ","region":"Africa","subregion":"Eastern Africa","emoji":"\ud83c\uddf9\ud83c\uddff"},{"name":"Thailand","iso3":"THA","iso2":"TH","region":"Asia","subregion":"South-Eastern Asia","emoji":"\ud83c\uddf9\ud83c\udded"},{"name":"Togo","iso3":"TGO","iso2":"TG","region":"Africa","subregion":"Western Africa","emoji":"\ud83c\uddf9\ud83c\uddec"},{"name":"Tokelau","iso3":"TKL","iso2":"TK","region":"Oceania","subregion":"Polynesia","emoji":"\ud83c\uddf9\ud83c\uddf0"},{"name":"Tonga","iso3":"TON","iso2":"TO","region":"Oceania","subregion":"Polynesia","emoji":"\ud83c\uddf9\ud83c\uddf4"},{"name":"Trinidad And Tobago","iso3":"TTO","iso2":"TT","region":"Americas","subregion":"Caribbean","emoji":"\ud83c\uddf9\ud83c\uddf9"},{"name":"Tunisia","iso3":"TUN","iso2":"TN","region":"Africa","subregion":"Northern Africa","emoji":"\ud83c\uddf9\ud83c\uddf3"},{"name":"Turkey","iso3":"TUR","iso2":"TR","region":"Asia","subregion":"Western Asia","emoji":"\ud83c\uddf9\ud83c\uddf7"},{"name":"Turkmenistan","iso3":"TKM","iso2":"TM","region":"Asia","subregion":"Central Asia","emoji":"\ud83c\uddf9\ud83c\uddf2"},{"name":"Turks And Caicos Islands","iso3":"TCA","iso2":"TC","region":"Americas","subregion":"Caribbean","emoji":"\ud83c\uddf9\ud83c\udde8"},{"name":"Tuvalu","iso3":"TUV","iso2":"TV","region":"Oceania","subregion":"Polynesia","emoji":"\ud83c\uddf9\ud83c\uddfb"},{"name":"Uganda","iso3":"UGA","iso2":"UG","region":"Africa","subregion":"Eastern Africa","emoji":"\ud83c\uddfa\ud83c\uddec"},{"name":"Ukraine","iso3":"UKR","iso2":"UA","region":"Europe","subregion":"Eastern Europe","emoji":"\ud83c\uddfa\ud83c\udde6"},{"name":"United Arab Emirates","iso3":"ARE","iso2":"AE","region":"Asia","subregion":"Western Asia","emoji":"\ud83c\udde6\ud83c\uddea"},{"name":"United Kingdom","iso3":"GBR","iso2":"GB","region":"Europe","subregion":"Northern Europe","emoji":"\ud83c\uddec\ud83c\udde7"},{"name":"United States","iso3":"USA","iso2":"US","region":"Americas","subregion":"Northern America","emoji":"\ud83c\uddfa\ud83c\uddf8"},{"name":"United States Minor Outlying Islands","iso3":"UMI","iso2":"UM","region":"Americas","subregion":"Northern America","emoji":"\ud83c\uddfa\ud83c\uddf2"},{"name":"Uruguay","iso3":"URY","iso2":"UY","region":"Americas","subregion":"South America","emoji":"\ud83c\uddfa\ud83c\uddfe"},{"name":"Uzbekistan","iso3":"UZB","iso2":"UZ","region":"Asia","subregion":"Central Asia","emoji":"\ud83c\uddfa\ud83c\uddff"},{"name":"Vanuatu","iso3":"VUT","iso2":"VU","region":"Oceania","subregion":"Melanesia","emoji":"\ud83c\uddfb\ud83c\uddfa"},{"name":"Vatican City State (Holy See)","iso3":"VAT","iso2":"VA","region":"Europe","subregion":"Southern Europe","emoji":"\ud83c\uddfb\ud83c\udde6"},{"name":"Venezuela","iso3":"VEN","iso2":"VE","region":"Americas","subregion":"South America","emoji":"\ud83c\uddfb\ud83c\uddea"},{"name":"Vietnam","iso3":"VNM","iso2":"VN","region":"Asia","subregion":"South-Eastern Asia","emoji":"\ud83c\uddfb\ud83c\uddf3"},{"name":"Virgin Islands (British)","iso3":"VGB","iso2":"VG","region":"Americas","subregion":"Caribbean","emoji":"\ud83c\uddfb\ud83c\uddec"},{"name":"Virgin Islands (US)","iso3":"VIR","iso2":"VI","region":"Americas","subregion":"Caribbean","emoji":"\ud83c\uddfb\ud83c\uddee"},{"name":"Wallis And Futuna Islands","iso3":"WLF","iso2":"WF","region":"Oceania","subregion":"Polynesia","emoji":"\ud83c\uddfc\ud83c\uddeb"},{"name":"Western Sahara","iso3":"ESH","iso2":"EH","region":"Africa","subregion":"Northern Africa","emoji":"\ud83c\uddea\ud83c\udded"},{"name":"Yemen","iso3":"YEM","iso2":"YE","region":"Asia","subregion":"Western Asia","emoji":"\ud83c\uddfe\ud83c\uddea"},{"name":"Zambia","iso3":"ZMB","iso2":"ZM","region":"Africa","subregion":"Eastern Africa","emoji":"\ud83c\uddff\ud83c\uddf2"},{"name":"Zimbabwe","iso3":"ZWE","iso2":"ZW","region":"Africa","subregion":"Eastern Africa","emoji":"\ud83c\uddff\ud83c\uddfc"}] \ No newline at end of file diff --git a/resources/js/bootstrap/fieldtypes.js b/resources/js/bootstrap/fieldtypes.js index 2608b7a861..d71db65a5c 100644 --- a/resources/js/bootstrap/fieldtypes.js +++ b/resources/js/bootstrap/fieldtypes.js @@ -24,6 +24,7 @@ import Routes from '../components/collections/Routes.vue'; import TitleFormats from '../components/collections/TitleFormats.vue'; import ColorFieldtype from '../components/fieldtypes/ColorFieldtype.vue'; import DateFieldtype from '../components/fieldtypes/DateFieldtype.vue'; +import DictionaryFieldtype from "../components/fieldtypes/DictionaryFieldtype.vue"; import FieldDisplayFieldtype from '../components/fieldtypes/FieldDisplayFieldtype.vue'; import FieldsFieldtype from '../components/fieldtypes/grid/FieldsFieldtype.vue'; import FilesFieldtype from '../components/fieldtypes/FilesFieldtype.vue'; @@ -85,6 +86,7 @@ Vue.component('collection_routes-fieldtype', Routes); Vue.component('collection_title_formats-fieldtype', TitleFormats); Vue.component('color-fieldtype', ColorFieldtype); Vue.component('date-fieldtype', DateFieldtype); +Vue.component('dictionary-fieldtype', DictionaryFieldtype); Vue.component('field_display-fieldtype', FieldDisplayFieldtype); Vue.component('fields-fieldtype', FieldsFieldtype); Vue.component('files-fieldtype', FilesFieldtype); diff --git a/resources/js/components/fieldtypes/DictionaryFieldtype.vue b/resources/js/components/fieldtypes/DictionaryFieldtype.vue new file mode 100644 index 0000000000..0554f4e30a --- /dev/null +++ b/resources/js/components/fieldtypes/DictionaryFieldtype.vue @@ -0,0 +1,179 @@ + + + + + diff --git a/src/Dictionaries/Countries.php b/src/Dictionaries/Countries.php new file mode 100644 index 0000000000..6ffa9bfc82 --- /dev/null +++ b/src/Dictionaries/Countries.php @@ -0,0 +1,39 @@ +getCountries()->pluck('name', 'iso3')->all(); + + return $this->getCountries()->mapWithKeys(function ($country) { + return [$country['iso3'] => "{$country['emoji']} {$country['name']}"]; + })->all(); + } + + /** + * Returns data for a single option, given the option's key. + * + * @param string $option + * @return array + */ + public function get(string $option): array + { + return $this->getCountries()->filter(fn ($value, $key) => $value['iso3'] === $option)->first(); + } + + private function getCountries(): Collection + { + return collect(json_decode(File::get(__DIR__.'/../../resources/dictionaries/countries.json'), true)); + } +} diff --git a/src/Dictionaries/Dictionary.php b/src/Dictionaries/Dictionary.php new file mode 100644 index 0000000000..f481393cb3 --- /dev/null +++ b/src/Dictionaries/Dictionary.php @@ -0,0 +1,23 @@ +map(fn ($class) => app($class)) + ->filter() + ->values(); + } + + public function find(string $dictionary): ?Dictionary + { + if ($class = app('statamic.dictionaries')->get($dictionary)) { + return app($class); + } + } +} diff --git a/src/Facades/Dictionary.php b/src/Facades/Dictionary.php new file mode 100644 index 0000000000..4bb8f8d8fa --- /dev/null +++ b/src/Facades/Dictionary.php @@ -0,0 +1,21 @@ + __('Options'), + 'fields' => [ + 'dictionary' => [ + 'display' => __('Dictionary'), + 'instructions' => 'Which dictionary do you want to select options from?', // TODO: move into translations file + 'type' => 'select', + 'options' => \Statamic\Facades\Dictionary::all() + ->mapWithKeys(fn ($dictionary) => [$dictionary->handle() => $dictionary->title()]) + ->all(), + 'taggable' => true, + 'validate' => 'required', + ], + ], + ], + [ + 'display' => __('Selection'), + 'fields' => [ + 'placeholder' => [ + 'display' => __('Placeholder'), + 'instructions' => __('statamic::fieldtypes.select.config.placeholder'), + 'type' => 'text', + 'default' => '', + ], + 'multiple' => [ + 'display' => __('Multiple'), + 'instructions' => __('statamic::fieldtypes.select.config.multiple'), + 'type' => 'toggle', + 'default' => false, + ], + 'max_items' => [ + 'display' => __('Max Items'), + 'instructions' => __('statamic::messages.max_items_instructions'), + 'min' => 1, + 'type' => 'integer', + ], + 'clearable' => [ + 'display' => __('Clearable'), + 'instructions' => __('statamic::fieldtypes.select.config.clearable'), + 'type' => 'toggle', + 'default' => false, + ], + 'searchable' => [ + 'display' => __('Searchable'), + 'instructions' => __('statamic::fieldtypes.select.config.searchable'), + 'type' => 'toggle', + 'default' => true, + ], + ], + ], + [ + 'display' => __('Data'), + 'fields' => [ + 'default' => [ + 'display' => __('Default Value'), + 'instructions' => __('statamic::messages.fields_default_instructions'), + 'type' => 'text', + ], + ], + ], + ]; + } + + public function preload(): array + { + $dictionary = \Statamic\Facades\Dictionary::find($this->config('dictionary')); + + return [ + 'options' => $dictionary->all(), + ]; + } + + public function augment($value): array + { + if ($this->multiple() && is_null($value)) { + return []; + } + + $dictionary = \Statamic\Facades\Dictionary::find($this->config('dictionary')); + + if ($this->multiple()) { + return collect($value)->map(function ($value) use ($dictionary) { + return $dictionary->get($value); + })->all(); + } + + return $dictionary->get($value); + } + + protected function multiple(): bool + { + return $this->config('multiple'); + } + + // TODO: graphql - how can we make it work since the keys will be dynamic? +} diff --git a/src/Providers/AddonServiceProvider.php b/src/Providers/AddonServiceProvider.php index 307bd9e76c..44d463ed76 100644 --- a/src/Providers/AddonServiceProvider.php +++ b/src/Providers/AddonServiceProvider.php @@ -10,6 +10,7 @@ use Illuminate\Support\Facades\Route; use Illuminate\Support\ServiceProvider; use Statamic\Actions\Action; +use Statamic\Dictionaries\Dictionary; use Statamic\Exceptions\NotBootedException; use Statamic\Extend\Manifest; use Statamic\Facades\Addon; @@ -55,6 +56,11 @@ abstract class AddonServiceProvider extends ServiceProvider */ protected $actions = []; + /** + * @var list> + */ + protected $dictionaries = []; + /** * @var list> */ @@ -187,6 +193,7 @@ public function boot() ->bootTags() ->bootScopes() ->bootActions() + ->bootDictionaries() ->bootFieldtypes() ->bootModifiers() ->bootWidgets() @@ -258,6 +265,15 @@ protected function bootActions() return $this; } + protected function bootDictionaries() + { + foreach ($this->dictionaries as $class) { + $class::register(); + } + + return $this; + } + protected function bootFieldtypes() { foreach ($this->fieldtypes as $class) { diff --git a/src/Providers/ExtensionServiceProvider.php b/src/Providers/ExtensionServiceProvider.php index 10a0da94a3..482c3a705a 100644 --- a/src/Providers/ExtensionServiceProvider.php +++ b/src/Providers/ExtensionServiceProvider.php @@ -6,6 +6,7 @@ use Illuminate\Support\ServiceProvider; use Statamic\Actions; use Statamic\Actions\Action; +use Statamic\Dictionaries\Dictionary; use Statamic\Extend\Manifest; use Statamic\Fields\Fieldtype; use Statamic\Fieldtypes; @@ -48,6 +49,10 @@ class ExtensionServiceProvider extends ServiceProvider Actions\Impersonate::class, ]; + protected $dictionaries = [ + \Statamic\Dictionaries\Countries::class, + ]; + protected $fieldtypes = [ Fieldtypes\Arr::class, Fieldtypes\AssetContainer::class, @@ -63,6 +68,7 @@ class ExtensionServiceProvider extends ServiceProvider Fieldtypes\Collections::class, Fieldtypes\Color::class, Fieldtypes\Date::class, + Fieldtypes\Dictionary::class, Fieldtypes\Entries::class, Fieldtypes\FieldDisplay::class, Fieldtypes\Files::class, @@ -263,6 +269,11 @@ protected function registerExtensions() 'directory' => 'Actions', 'extensions' => $this->actions, ], + 'dictionaries' => [ + 'class' => Dictionary::class, + 'directory' => 'Dictionaries', + 'extensions' => $this->dictionaries + ], 'fieldtypes' => [ 'class' => Fieldtype::class, 'directory' => 'Fieldtypes', diff --git a/src/Tags/Collection/Entries.php b/src/Tags/Collection/Entries.php index dc910752d6..e11ef1c762 100644 --- a/src/Tags/Collection/Entries.php +++ b/src/Tags/Collection/Entries.php @@ -190,8 +190,8 @@ protected function query() $this->queryTaxonomies($query); $this->queryRedirects($query); $this->queryConditions($query); - $this->queryScopes($query); $this->queryOrderBys($query); + $this->queryScopes($query); return $query; } diff --git a/tests/Antlers/Runtime/ArraysTest.php b/tests/Antlers/Runtime/ArraysTest.php index 1cef56e994..28c0549c2b 100644 --- a/tests/Antlers/Runtime/ArraysTest.php +++ b/tests/Antlers/Runtime/ArraysTest.php @@ -25,13 +25,13 @@ public function test_dictionary_access() 'address' => [ 'city' => 'City Name', 'region' => 'Region Name', - 'country' => 'Country Name', + 'country' => 'Countries Name', ], ]; $this->assertSame('City Name', $this->renderString('{{ address:city }}', $data)); $this->assertSame('Region Name', $this->renderString('{{ address:region }}', $data)); - $this->assertSame('Country Name', $this->renderString('{{ address:country }}', $data)); + $this->assertSame('Countries Name', $this->renderString('{{ address:country }}', $data)); } public function test_multi_dimensional_array_access() @@ -407,9 +407,9 @@ public function test_creation_of_new_array_elements() Two: Two Three: Three -One: -Two: -Three: +One: +Two: +Three: From 836b5291615d3baa394d67f0bdc58e2a0b44c967 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Fri, 7 Jun 2024 19:17:09 +0100 Subject: [PATCH 02/76] wip --- src/Dictionaries/Countries.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Dictionaries/Countries.php b/src/Dictionaries/Countries.php index 6ffa9bfc82..01248eb9d4 100644 --- a/src/Dictionaries/Countries.php +++ b/src/Dictionaries/Countries.php @@ -14,8 +14,6 @@ class Countries extends Dictionary */ public function all(): array { -// return $this->getCountries()->pluck('name', 'iso3')->all(); - return $this->getCountries()->mapWithKeys(function ($country) { return [$country['iso3'] => "{$country['emoji']} {$country['name']}"]; })->all(); From ee81cd29f935ef547eafc3ac3fca4d014caeab77 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Thu, 20 Jun 2024 13:42:02 +0100 Subject: [PATCH 03/76] Make it possible to search dictionaries --- .../fieldtypes/DictionaryFieldtype.vue | 48 +++++++++++++++---- routes/cp.php | 2 + src/Dictionaries/Countries.php | 20 ++++---- src/Dictionaries/Dictionary.php | 3 +- src/Fieldtypes/Dictionary.php | 14 ++++-- .../DictionaryFieldtypeController.php | 19 ++++++++ 6 files changed, 82 insertions(+), 24 deletions(-) create mode 100644 src/Http/Controllers/CP/Fieldtypes/DictionaryFieldtypeController.php diff --git a/resources/js/components/fieldtypes/DictionaryFieldtype.vue b/resources/js/components/fieldtypes/DictionaryFieldtype.vue index 0554f4e30a..8ec52baded 100644 --- a/resources/js/components/fieldtypes/DictionaryFieldtype.vue +++ b/resources/js/components/fieldtypes/DictionaryFieldtype.vue @@ -9,18 +9,18 @@ :name="name" :clearable="config.clearable" :disabled="config.disabled || isReadOnly || (config.multiple && limitReached)" - :options="options" + :options="normalizeInputOptions(options)" :placeholder="__(config.placeholder)" - :searchable="config.searchable || config.taggable" + :searchable="true" :taggable="config.taggable" :push-tags="config.push_tags" :multiple="config.multiple" :reset-on-options-change="resetOnOptionsChange" :close-on-select="true" :value="selectedOptions" - :create-option="(value) => ({ value, label: value })" @input="vueSelectUpdated" @focus="$emit('focus')" + @search="search" @search:focus="$emit('focus')" @search:blur="$emit('blur')"> @@ -85,7 +85,6 @@ import HasInputOptions from './HasInputOptions.js' import { SortableList } from '../sortable/Sortable'; import PositionsSelectOptions from '../../mixins/PositionsSelectOptions'; - export default { mixins: [Fieldtype, HasInputOptions, PositionsSelectOptions], @@ -94,19 +93,28 @@ export default { SortableList }, + data() { + return { + options: {}, + selectedOptionData: this.meta.selectedOptions, + } + }, + computed: { selectedOptions() { let selections = this.value || []; + if (typeof selections === 'string' || typeof selections === 'number') { selections = [selections]; } + return selections.map(value => { - return _.findWhere(this.options, {value}) || { value, label: value }; - }); - }, + let option = this.selectedOptionData.find(option => option.value === value); + + if (! option) return {value, label: value}; - options() { - return this.normalizeInputOptions(this.meta.options); // duncan: all that's changed here is this.config.options to this.meta.options + return {value: option.value, label: option.label}; + }); }, replicatorPreview() { @@ -155,7 +163,11 @@ export default { } return 'text-gray'; - } + }, + }, + + mounted() { + this.request(); }, methods: { @@ -166,6 +178,7 @@ export default { vueSelectUpdated(value) { if (this.config.multiple) { this.update(value.map(v => v.value)); + value.forEach((option) => this.selectedOptionData.push(option)); } else { if (value) { this.update(value.value) @@ -174,6 +187,21 @@ export default { } } }, + + request(params = {}) { + params = {...params}; + + return this.$axios.get(this.meta.url, { params }).then(response => { + this.options = response.data.data; + return Promise.resolve(response); + }); + }, + + search(search, loading) { + loading(true); + + this.request({ search }).then(response => loading(false)); + }, } }; diff --git a/routes/cp.php b/routes/cp.php index 536ab6b926..9cfd511b65 100644 --- a/routes/cp.php +++ b/routes/cp.php @@ -45,6 +45,7 @@ use Statamic\Http\Controllers\CP\Fields\FieldsetController; use Statamic\Http\Controllers\CP\Fields\FieldtypesController; use Statamic\Http\Controllers\CP\Fields\MetaController; +use Statamic\Http\Controllers\CP\Fieldtypes\DictionaryFieldtypeController; use Statamic\Http\Controllers\CP\Fieldtypes\FilesFieldtypeController; use Statamic\Http\Controllers\CP\Fieldtypes\MarkdownFieldtypeController; use Statamic\Http\Controllers\CP\Fieldtypes\RelationshipFieldtypeController; @@ -306,6 +307,7 @@ Route::get('relationship/filters', [RelationshipFieldtypeController::class, 'filters'])->name('relationship.filters'); Route::post('markdown', [MarkdownFieldtypeController::class, 'preview'])->name('markdown.preview'); Route::post('files/upload', [FilesFieldtypeController::class, 'upload'])->name('files.upload'); + Route::get('dictionaries/{dictionary}', DictionaryFieldtypeController::class)->name('dictionary-fieldtype'); }); Route::group(['prefix' => 'api', 'as' => 'api.'], function () { diff --git a/src/Dictionaries/Countries.php b/src/Dictionaries/Countries.php index 01248eb9d4..6f5ccd3c48 100644 --- a/src/Dictionaries/Countries.php +++ b/src/Dictionaries/Countries.php @@ -9,21 +9,23 @@ class Countries extends Dictionary { /** * Returns all options. - * - * @return array */ - public function all(): array + public function options(?string $search = null): array { - return $this->getCountries()->mapWithKeys(function ($country) { - return [$country['iso3'] => "{$country['emoji']} {$country['name']}"]; - })->all(); + return $this->getCountries() + ->when($search ?? false, function ($collection) use ($search) { + return $collection->filter(function (array $country) use ($search) { + return str_contains(strtolower($country['name']), strtolower($search)); + }); + }) + ->mapWithKeys(function (array $country) { + return [$country['iso3'] => "{$country['emoji']} {$country['name']}"]; + }) + ->all(); } /** * Returns data for a single option, given the option's key. - * - * @param string $option - * @return array */ public function get(string $option): array { diff --git a/src/Dictionaries/Dictionary.php b/src/Dictionaries/Dictionary.php index f481393cb3..9fde3df962 100644 --- a/src/Dictionaries/Dictionary.php +++ b/src/Dictionaries/Dictionary.php @@ -2,7 +2,6 @@ namespace Statamic\Dictionaries; -use Illuminate\Support\Collection; use Statamic\Extend\HasHandle; use Statamic\Extend\HasTitle; use Statamic\Extend\RegistersItself; @@ -13,7 +12,7 @@ abstract class Dictionary protected static $binding = 'dictionaries'; - abstract public function all(): array; + abstract public function options(string $search = null): array; abstract public function get(string $option); diff --git a/src/Fieldtypes/Dictionary.php b/src/Fieldtypes/Dictionary.php index 96a16aed54..3e9a5970ad 100644 --- a/src/Fieldtypes/Dictionary.php +++ b/src/Fieldtypes/Dictionary.php @@ -79,10 +79,13 @@ protected function configFieldItems(): array public function preload(): array { - $dictionary = \Statamic\Facades\Dictionary::find($this->config('dictionary')); - return [ - 'options' => $dictionary->all(), + 'url' => cp_route('dictionary-fieldtype', $this->dictionary()->handle()), + 'selectedOptions' => collect($this->dictionary()->options()) + ->only($this->field->value()) + ->map(fn ($label, $value) => ['value' => $value, 'label' => $label]) + ->values() + ->all(), ]; } @@ -108,5 +111,10 @@ protected function multiple(): bool return $this->config('multiple'); } + private function dictionary(): \Statamic\Dictionaries\Dictionary + { + return \Statamic\Facades\Dictionary::find($this->config('dictionary')); + } + // TODO: graphql - how can we make it work since the keys will be dynamic? } diff --git a/src/Http/Controllers/CP/Fieldtypes/DictionaryFieldtypeController.php b/src/Http/Controllers/CP/Fieldtypes/DictionaryFieldtypeController.php new file mode 100644 index 0000000000..0eb80184ae --- /dev/null +++ b/src/Http/Controllers/CP/Fieldtypes/DictionaryFieldtypeController.php @@ -0,0 +1,19 @@ + $dictionary->options($request->search), + ]; + } +} From 2ea020a21ca93230dc13ed8a4fe19357bd4db600 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Thu, 20 Jun 2024 21:53:16 +0100 Subject: [PATCH 04/76] wip --- src/Dictionaries/Dictionary.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Dictionaries/Dictionary.php b/src/Dictionaries/Dictionary.php index 9fde3df962..66756efac6 100644 --- a/src/Dictionaries/Dictionary.php +++ b/src/Dictionaries/Dictionary.php @@ -12,11 +12,7 @@ abstract class Dictionary protected static $binding = 'dictionaries'; - abstract public function options(string $search = null): array; + abstract public function options(?string $search = null): array; abstract public function get(string $option); - - // Goal: To have an easy way to point at a JSON or YAML file and have it return its data - // If the data is a key/value pair, it should return the key as the value and the value as the label - // If it's not, try and identify the key and value and return them as such } From 0e3c63d8a4aa4911e5c52348077aaa7af2141b0c Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Thu, 20 Jun 2024 22:25:17 +0100 Subject: [PATCH 05/76] wip --- resources/js/components/fieldtypes/DictionaryFieldtype.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/js/components/fieldtypes/DictionaryFieldtype.vue b/resources/js/components/fieldtypes/DictionaryFieldtype.vue index 8ec52baded..67fb2607e5 100644 --- a/resources/js/components/fieldtypes/DictionaryFieldtype.vue +++ b/resources/js/components/fieldtypes/DictionaryFieldtype.vue @@ -182,6 +182,7 @@ export default { } else { if (value) { this.update(value.value) + this.selectedOptionData.push(value) } else { this.update(null); } From 47136ee8a55e91fdb5e082195a0d898e237f46f9 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Fri, 21 Jun 2024 12:14:54 +0100 Subject: [PATCH 06/76] tweaks --- src/Dictionaries/Countries.php | 10 ++-------- src/Dictionaries/Dictionary.php | 8 +++++++- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Dictionaries/Countries.php b/src/Dictionaries/Countries.php index 6f5ccd3c48..371c20cb2e 100644 --- a/src/Dictionaries/Countries.php +++ b/src/Dictionaries/Countries.php @@ -7,9 +7,6 @@ class Countries extends Dictionary { - /** - * Returns all options. - */ public function options(?string $search = null): array { return $this->getCountries() @@ -24,12 +21,9 @@ public function options(?string $search = null): array ->all(); } - /** - * Returns data for a single option, given the option's key. - */ - public function get(string $option): array + public function get(string $key): array { - return $this->getCountries()->filter(fn ($value, $key) => $value['iso3'] === $option)->first(); + return $this->getCountries()->filter(fn ($value, $key) => $value['iso3'] === $key)->first(); } private function getCountries(): Collection diff --git a/src/Dictionaries/Dictionary.php b/src/Dictionaries/Dictionary.php index 66756efac6..afbf986158 100644 --- a/src/Dictionaries/Dictionary.php +++ b/src/Dictionaries/Dictionary.php @@ -12,7 +12,13 @@ abstract class Dictionary protected static $binding = 'dictionaries'; + /** + * Returns all options, optionally filtered by a search term. + */ abstract public function options(?string $search = null): array; - abstract public function get(string $option); + /** + * Returns data for a single option, given the option's key. + */ + abstract public function get(string $key); } From 5f49d4866a05b0d0e36e40f53b47b6f72778b0d6 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Fri, 21 Jun 2024 12:43:00 +0100 Subject: [PATCH 07/76] wip --- src/Dictionaries/Countries.php | 4 ++-- src/Dictionaries/Dictionary.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Dictionaries/Countries.php b/src/Dictionaries/Countries.php index 371c20cb2e..55cb6921c2 100644 --- a/src/Dictionaries/Countries.php +++ b/src/Dictionaries/Countries.php @@ -21,9 +21,9 @@ public function options(?string $search = null): array ->all(); } - public function get(string $key): array + public function get(string $key): string|array { - return $this->getCountries()->filter(fn ($value, $key) => $value['iso3'] === $key)->first(); + return $this->getCountries()->filter(fn (array $country) => $country['iso3'] === $key)->first(); } private function getCountries(): Collection diff --git a/src/Dictionaries/Dictionary.php b/src/Dictionaries/Dictionary.php index afbf986158..ecb323c322 100644 --- a/src/Dictionaries/Dictionary.php +++ b/src/Dictionaries/Dictionary.php @@ -20,5 +20,5 @@ abstract public function options(?string $search = null): array; /** * Returns data for a single option, given the option's key. */ - abstract public function get(string $key); + abstract public function get(string $key): string|array; } From 923b8046ed2a1ecd94f916b1e29d6a6800c722a0 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Fri, 21 Jun 2024 12:43:24 +0100 Subject: [PATCH 08/76] Leave this return type open --- src/Fieldtypes/Dictionary.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Fieldtypes/Dictionary.php b/src/Fieldtypes/Dictionary.php index 3e9a5970ad..3fafa24454 100644 --- a/src/Fieldtypes/Dictionary.php +++ b/src/Fieldtypes/Dictionary.php @@ -89,7 +89,7 @@ public function preload(): array ]; } - public function augment($value): array + public function augment($value) { if ($this->multiple() && is_null($value)) { return []; From f969de22f076dc63de38bd6959e23ac38a1f5246 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Fri, 21 Jun 2024 12:43:30 +0100 Subject: [PATCH 09/76] Timezone dictionary --- src/Dictionaries/Timezones.php | 19 +++++++++++++++++++ src/Providers/ExtensionServiceProvider.php | 1 + 2 files changed, 20 insertions(+) create mode 100644 src/Dictionaries/Timezones.php diff --git a/src/Dictionaries/Timezones.php b/src/Dictionaries/Timezones.php new file mode 100644 index 0000000000..b8351312c2 --- /dev/null +++ b/src/Dictionaries/Timezones.php @@ -0,0 +1,19 @@ +filter(fn ($timezone) => $search ? str_contains(strtolower($timezone), strtolower($search)) : true) + ->mapWithKeys(fn ($timezone) => [$timezone => $timezone]) + ->all(); + } + + public function get(string $option): string|array + { + return $option; + } +} diff --git a/src/Providers/ExtensionServiceProvider.php b/src/Providers/ExtensionServiceProvider.php index 482c3a705a..9a6d35d878 100644 --- a/src/Providers/ExtensionServiceProvider.php +++ b/src/Providers/ExtensionServiceProvider.php @@ -51,6 +51,7 @@ class ExtensionServiceProvider extends ServiceProvider protected $dictionaries = [ \Statamic\Dictionaries\Countries::class, + \Statamic\Dictionaries\Timezones::class, ]; protected $fieldtypes = [ From 4cc6bfe087fbf4acf37894ee35ed81f4f4be07fb Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Fri, 21 Jun 2024 13:05:48 +0100 Subject: [PATCH 10/76] tweaks to the fieldtype --- resources/lang/en/fieldtypes.php | 1 + src/Fieldtypes/Dictionary.php | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/resources/lang/en/fieldtypes.php b/resources/lang/en/fieldtypes.php index a2776c2c38..6d526d6b4b 100644 --- a/resources/lang/en/fieldtypes.php +++ b/resources/lang/en/fieldtypes.php @@ -72,6 +72,7 @@ 'date.config.time_enabled' => 'Enable the timepicker.', 'date.config.time_seconds_enabled' => 'Show seconds in the timepicker.', 'date.title' => 'Date', + 'dictionary.config.dictionary' => 'Please select the dictionary you wish to pull options from.', 'entries.config.create' => 'Allow creation of new entries.', 'entries.config.collections' => 'Choose which collections the user can select from.', 'entries.config.query_scopes' => 'Choose which query scopes should be applied when retrieving selectable entries.', diff --git a/src/Fieldtypes/Dictionary.php b/src/Fieldtypes/Dictionary.php index 3fafa24454..a2fe7e3a95 100644 --- a/src/Fieldtypes/Dictionary.php +++ b/src/Fieldtypes/Dictionary.php @@ -7,9 +7,10 @@ class Dictionary extends Fieldtype { - protected $categories = ['relationship']; + protected $categories = ['controls', 'relationship']; protected $selectableInForms = true; // TODO: include in frontend forms protected $indexComponent = 'tags'; + protected $icon = 'select'; protected function configFieldItems(): array { @@ -19,7 +20,7 @@ protected function configFieldItems(): array 'fields' => [ 'dictionary' => [ 'display' => __('Dictionary'), - 'instructions' => 'Which dictionary do you want to select options from?', // TODO: move into translations file + 'instructions' => __('statamic::fieldtypes.dictionary.config.dictionary'), 'type' => 'select', 'options' => \Statamic\Facades\Dictionary::all() ->mapWithKeys(fn ($dictionary) => [$dictionary->handle() => $dictionary->title()]) From 4ac1f236e87c7e35911f6bf82a60f6217ae32b26 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Fri, 21 Jun 2024 14:32:56 +0100 Subject: [PATCH 11/76] wip --- src/Fieldtypes/Dictionary.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Fieldtypes/Dictionary.php b/src/Fieldtypes/Dictionary.php index a2fe7e3a95..1555f6a0e9 100644 --- a/src/Fieldtypes/Dictionary.php +++ b/src/Fieldtypes/Dictionary.php @@ -92,7 +92,7 @@ public function preload(): array public function augment($value) { - if ($this->multiple() && is_null($value)) { + if (is_null($value)) { return []; } From 19bb84d5cb45ad2e177752f093f759339998841e Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Fri, 21 Jun 2024 14:39:35 +0100 Subject: [PATCH 12/76] Forms support --- .../forms/fields/dictionary.antlers.html | 21 +++++++++++++++++++ .../views/forms/automagic-email.antlers.html | 2 +- src/Fieldtypes/Dictionary.php | 7 +++++++ 3 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 resources/views/extend/forms/fields/dictionary.antlers.html diff --git a/resources/views/extend/forms/fields/dictionary.antlers.html b/resources/views/extend/forms/fields/dictionary.antlers.html new file mode 100644 index 0000000000..b093f6a026 --- /dev/null +++ b/resources/views/extend/forms/fields/dictionary.antlers.html @@ -0,0 +1,21 @@ + diff --git a/resources/views/forms/automagic-email.antlers.html b/resources/views/forms/automagic-email.antlers.html index f8fd90528b..cab5234e64 100644 --- a/resources/views/forms/automagic-email.antlers.html +++ b/resources/views/forms/automagic-email.antlers.html @@ -12,7 +12,7 @@ {{ elseif fieldtype == "radio" }} {{ value:label ?? value }} - {{ elseif fieldtype == "select" || fieldtype == "checkboxes" }} + {{ elseif fieldtype == "select" || fieldtype == "checkboxes" || fieldtype == "dictionary" }} {{ value }}{{ label ?? value }}{{ if !last }}, {{ /if }}{{ /value }} {{ elseif value|is_iterable }} diff --git a/src/Fieldtypes/Dictionary.php b/src/Fieldtypes/Dictionary.php index 1555f6a0e9..5c5f1c5743 100644 --- a/src/Fieldtypes/Dictionary.php +++ b/src/Fieldtypes/Dictionary.php @@ -107,6 +107,13 @@ public function augment($value) return $dictionary->get($value); } + public function extraRenderableFieldData(): array + { + return [ + 'options' => $this->dictionary()->options(), + ]; + } + protected function multiple(): bool { return $this->config('multiple'); From 8ae22f7d48e55b18cb3a2539749d9d1796388cf1 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Mon, 1 Jul 2024 19:23:42 +0100 Subject: [PATCH 13/76] Allow adding config fields to dictionaries --- resources/js/bootstrap/fieldtypes.js | 2 + .../fieldtypes/DictionaryFields.vue | 105 ++++++++++++++++++ .../fieldtypes/DictionaryFieldtype.vue | 11 +- resources/lang/en/messages.php | 1 + src/Dictionaries/Countries.php | 15 +++ src/Dictionaries/Dictionary.php | 18 ++- src/Dictionaries/DictionaryRepository.php | 18 ++- .../DictionaryNotFoundException.php | 11 ++ .../UndefinedDictionaryException.php | 13 +++ src/Facades/Dictionary.php | 2 +- src/Fieldtypes/Dictionary.php | 38 ++++--- src/Fieldtypes/DictionaryFields.php | 100 +++++++++++++++++ .../DictionaryFieldtypeController.php | 33 +++++- src/Providers/ExtensionServiceProvider.php | 1 + 14 files changed, 345 insertions(+), 23 deletions(-) create mode 100644 resources/js/components/fieldtypes/DictionaryFields.vue create mode 100644 src/Exceptions/DictionaryNotFoundException.php create mode 100644 src/Exceptions/UndefinedDictionaryException.php create mode 100644 src/Fieldtypes/DictionaryFields.php diff --git a/resources/js/bootstrap/fieldtypes.js b/resources/js/bootstrap/fieldtypes.js index d71db65a5c..8a4e8f773e 100644 --- a/resources/js/bootstrap/fieldtypes.js +++ b/resources/js/bootstrap/fieldtypes.js @@ -25,6 +25,7 @@ import TitleFormats from '../components/collections/TitleFormats.vue'; import ColorFieldtype from '../components/fieldtypes/ColorFieldtype.vue'; import DateFieldtype from '../components/fieldtypes/DateFieldtype.vue'; import DictionaryFieldtype from "../components/fieldtypes/DictionaryFieldtype.vue"; +import DictionaryFields from "../components/fieldtypes/DictionaryFields.vue"; import FieldDisplayFieldtype from '../components/fieldtypes/FieldDisplayFieldtype.vue'; import FieldsFieldtype from '../components/fieldtypes/grid/FieldsFieldtype.vue'; import FilesFieldtype from '../components/fieldtypes/FilesFieldtype.vue'; @@ -87,6 +88,7 @@ Vue.component('collection_title_formats-fieldtype', TitleFormats); Vue.component('color-fieldtype', ColorFieldtype); Vue.component('date-fieldtype', DateFieldtype); Vue.component('dictionary-fieldtype', DictionaryFieldtype); +Vue.component('dictionary_fields-fieldtype', DictionaryFields); Vue.component('field_display-fieldtype', FieldDisplayFieldtype); Vue.component('fields-fieldtype', FieldsFieldtype); Vue.component('files-fieldtype', FilesFieldtype); diff --git a/resources/js/components/fieldtypes/DictionaryFields.vue b/resources/js/components/fieldtypes/DictionaryFields.vue new file mode 100644 index 0000000000..bca2ad5dd7 --- /dev/null +++ b/resources/js/components/fieldtypes/DictionaryFields.vue @@ -0,0 +1,105 @@ + + + + + diff --git a/resources/js/components/fieldtypes/DictionaryFieldtype.vue b/resources/js/components/fieldtypes/DictionaryFieldtype.vue index 67fb2607e5..e991b0fe34 100644 --- a/resources/js/components/fieldtypes/DictionaryFieldtype.vue +++ b/resources/js/components/fieldtypes/DictionaryFieldtype.vue @@ -164,10 +164,14 @@ export default { return 'text-gray'; }, + + configParameter() { + return utf8btoa(JSON.stringify(this.config)); + }, }, mounted() { - this.request(); + this.request(); }, methods: { @@ -190,7 +194,10 @@ export default { }, request(params = {}) { - params = {...params}; + params = { + config: this.configParameter, + ...params, + } return this.$axios.get(this.meta.url, { params }).then(response => { this.options = response.data.data; diff --git a/resources/lang/en/messages.php b/resources/lang/en/messages.php index 324478e73c..368ed3984a 100644 --- a/resources/lang/en/messages.php +++ b/resources/lang/en/messages.php @@ -69,6 +69,7 @@ 'collections_sort_direction_instructions' => 'The default sort direction.', 'collections_preview_target_refresh_instructions' => 'Automatically refresh the preview while editing. Disabling this will use postMessage.', 'collections_taxonomies_instructions' => 'Connect entries in this collection to taxonomies. Fields will be automatically added to publish forms.', + 'dictionaries_countries_region_instructions' => 'Select a region to filter the list of countries.', 'duplicate_action_warning_localization' => 'This entry is a localization. The origin entry will be duplicated.', 'duplicate_action_warning_localizations' => 'One or more selected entries are localizations. In those cases, the origin entry will be duplicated instead.', 'duplicate_action_localizations_confirmation' => 'Are you sure you want to run this action? Localizations will also be duplicated.', diff --git a/src/Dictionaries/Countries.php b/src/Dictionaries/Countries.php index 55cb6921c2..0f6559257a 100644 --- a/src/Dictionaries/Countries.php +++ b/src/Dictionaries/Countries.php @@ -10,6 +10,9 @@ class Countries extends Dictionary public function options(?string $search = null): array { return $this->getCountries() + ->when($this->context['region'] ?? false, function ($collection) { + return $collection->where('region', $this->context['region']); + }) ->when($search ?? false, function ($collection) use ($search) { return $collection->filter(function (array $country) use ($search) { return str_contains(strtolower($country['name']), strtolower($search)); @@ -26,6 +29,18 @@ public function get(string $key): string|array return $this->getCountries()->filter(fn (array $country) => $country['iso3'] === $key)->first(); } + protected function fieldItems() + { + return [ + 'region' => [ + 'display' => __('Region'), + 'instructions' => __('statamic::messages.dictionaries_countries_region_instructions'), + 'type' => 'select', + 'options' => $this->getCountries()->unique('region')->pluck('region', 'region')->filter()->all(), + ], + ]; + } + private function getCountries(): Collection { return collect(json_decode(File::get(__DIR__.'/../../resources/dictionaries/countries.json'), true)); diff --git a/src/Dictionaries/Dictionary.php b/src/Dictionaries/Dictionary.php index ecb323c322..ce2b95f46c 100644 --- a/src/Dictionaries/Dictionary.php +++ b/src/Dictionaries/Dictionary.php @@ -2,16 +2,20 @@ namespace Statamic\Dictionaries; +use Statamic\Extend\HasFields; use Statamic\Extend\HasHandle; use Statamic\Extend\HasTitle; use Statamic\Extend\RegistersItself; abstract class Dictionary { - use HasHandle, HasTitle, RegistersItself; + use HasFields, HasHandle, HasTitle, RegistersItself; protected static $binding = 'dictionaries'; + protected $fields = []; + protected $context = []; + /** * Returns all options, optionally filtered by a search term. */ @@ -21,4 +25,16 @@ abstract public function options(?string $search = null): array; * Returns data for a single option, given the option's key. */ abstract public function get(string $key): string|array; + + public function context($context) + { + $this->context = $context; + + return $this; + } + + protected function fieldItems() + { + return $this->fields; + } } diff --git a/src/Dictionaries/DictionaryRepository.php b/src/Dictionaries/DictionaryRepository.php index 7455328982..c57a774033 100644 --- a/src/Dictionaries/DictionaryRepository.php +++ b/src/Dictionaries/DictionaryRepository.php @@ -14,10 +14,22 @@ public function all(): Collection ->values(); } - public function find(string $dictionary): ?Dictionary + public function find(string $handle, array $context = []): ?Dictionary { - if ($class = app('statamic.dictionaries')->get($dictionary)) { - return app($class); + if (! $dictionary = app('statamic.dictionaries')->get($handle)) { + return null; } + + $dictionary = app($dictionary); + + if (! $dictionary) { + return null; + } + + if (! method_exists($dictionary, 'context')) { + return $dictionary; + } + + return $dictionary->context($context); } } diff --git a/src/Exceptions/DictionaryNotFoundException.php b/src/Exceptions/DictionaryNotFoundException.php new file mode 100644 index 0000000000..dd672f1985 --- /dev/null +++ b/src/Exceptions/DictionaryNotFoundException.php @@ -0,0 +1,11 @@ +dictionaryHandle}] not found"); + } +} diff --git a/src/Exceptions/UndefinedDictionaryException.php b/src/Exceptions/UndefinedDictionaryException.php new file mode 100644 index 0000000000..a9c1f0f56d --- /dev/null +++ b/src/Exceptions/UndefinedDictionaryException.php @@ -0,0 +1,13 @@ + __('Options'), 'fields' => [ 'dictionary' => [ - 'display' => __('Dictionary'), - 'instructions' => __('statamic::fieldtypes.dictionary.config.dictionary'), - 'type' => 'select', - 'options' => \Statamic\Facades\Dictionary::all() - ->mapWithKeys(fn ($dictionary) => [$dictionary->handle() => $dictionary->title()]) - ->all(), - 'taggable' => true, - 'validate' => 'required', + 'type' => 'dictionary_fields', + 'hide_display' => true, + 'full_width_setting' => true, ], ], ], @@ -96,7 +93,7 @@ public function augment($value) return []; } - $dictionary = \Statamic\Facades\Dictionary::find($this->config('dictionary')); + $dictionary = $this->dictionary(); if ($this->multiple()) { return collect($value)->map(function ($value) use ($dictionary) { @@ -119,9 +116,24 @@ protected function multiple(): bool return $this->config('multiple'); } - private function dictionary(): \Statamic\Dictionaries\Dictionary + public function dictionary(): \Statamic\Dictionaries\Dictionary { - return \Statamic\Facades\Dictionary::find($this->config('dictionary')); + if (! $this->config('dictionary')) { + throw new UndefinedDictionaryException(); + } + + $config = $this->config('dictionary'); + + $handle = is_array($config) ? Arr::get($config, 'type') : $config; + $context = is_array($config) ? Arr::except($config, 'type') : []; + + $dictionary = \Statamic\Facades\Dictionary::find($handle, $context); + + if (! $dictionary) { + throw new DictionaryNotFoundException($handle); + } + + return $dictionary; } // TODO: graphql - how can we make it work since the keys will be dynamic? diff --git a/src/Fieldtypes/DictionaryFields.php b/src/Fieldtypes/DictionaryFields.php new file mode 100644 index 0000000000..e3d303477c --- /dev/null +++ b/src/Fieldtypes/DictionaryFields.php @@ -0,0 +1,100 @@ + 'type', + 'field' => [ + 'display' => __('Dictionary'), + 'instructions' => __('statamic::fieldtypes.dictionary.config.dictionary'), + 'type' => 'select', + 'options' => Dictionary::all() + ->mapWithKeys(fn ($dictionary) => [$dictionary->handle() => $dictionary->title()]) + ->all(), + 'max_items' => 1, + 'validate' => 'required', + ], + ]]); + + return [ + 'type' => [ + 'fields' => $typeField->toPublishArray(), + 'meta' => $typeField->meta(), + ], + 'dictionaries' => Dictionary::all()->mapWithKeys(function (\Statamic\Dictionaries\Dictionary $dictionary) { + return [$dictionary->handle() => [ + 'fields' => $dictionary->fields()->toPublishArray(), + 'meta' => $dictionary->fields()->meta(), + 'defaults' => $dictionary->fields()->all()->map(function ($field) { + return $field->fieldtype()->preProcess($field->defaultValue()); + }), + ]]; + }), + ]; + } + + public function preProcess($data): array + { + if (is_null($data)) { + return ['type' => null]; + } + + if (is_string($data)) { + return ['type' => $data]; + } + + $dictionary = Dictionary::find($data['type']); + + return array_merge( + ['type' => $data['type']], + $dictionary->fields()->addValues($data)->preProcess()->values()->all() + ); + } + + public function process($data): string|array + { + $dictionary = Dictionary::find($data['type']); + $values = $dictionary->fields()->addValues($data)->process()->values(); + + if ($values->filter()->isEmpty()) { + return $dictionary->handle(); + } + + return array_merge(['type' => $dictionary->handle()], $values->all()); + } + + public function extraRules(): array + { + if (! $dictionary = Arr::get($this->field->value(), 'type')) { + return [ + $this->field->handle().'.type' => 'required', + ]; + } + + $dictionary = Dictionary::find($dictionary); + + $rules = $dictionary + ->fields() + ->addValues((array) $this->field->value()) + ->validator() + ->withContext([ + 'prefix' => $this->field->handle().'.', + ]) + ->rules(); + + return collect($rules)->mapWithKeys(function ($rules, $handle) { + return [$this->field->handle().'.'.$handle => $rules]; + })->all(); + } +} diff --git a/src/Http/Controllers/CP/Fieldtypes/DictionaryFieldtypeController.php b/src/Http/Controllers/CP/Fieldtypes/DictionaryFieldtypeController.php index 0eb80184ae..5d442c916f 100644 --- a/src/Http/Controllers/CP/Fieldtypes/DictionaryFieldtypeController.php +++ b/src/Http/Controllers/CP/Fieldtypes/DictionaryFieldtypeController.php @@ -2,18 +2,45 @@ namespace Statamic\Http\Controllers\CP\Fieldtypes; +use Facades\Statamic\Fields\FieldtypeRepository as Fieldtype; use Illuminate\Http\Request; -use Statamic\Facades\Dictionary; +use Statamic\Fields\Field; use Statamic\Http\Controllers\CP\CpController; class DictionaryFieldtypeController extends CpController { public function __invoke(Request $request, string $dictionary) { - $dictionary = Dictionary::find($dictionary); + $fieldtype = $this->fieldtype($request); return [ - 'data' => $dictionary->options($request->search), + 'data' => $fieldtype->dictionary()->options($request->search), ]; } + + protected function fieldtype($request) + { + $config = $this->getConfig($request); + + return Fieldtype::find($config['type'])->setField( + new Field('relationship', $config) + ); + } + + private function getConfig($request) + { + // The fieldtype base64-encodes the config. + $json = base64_decode($request->config); + + // The json may include unicode characters, so we'll try to convert it to UTF-8. + // See https://github.com/statamic/cms/issues/566 + $utf8 = mb_convert_encoding($json, 'UTF-8', mb_list_encodings()); + + // In PHP 8.1 there's a bug where encoding will return null. It's fixed in 8.1.2. + // In this case, we'll fall back to the original JSON, but without the encoding. + // Issue #566 may still occur, but it's better than failing completely. + $json = empty($utf8) ? $json : $utf8; + + return json_decode($json, true); + } } diff --git a/src/Providers/ExtensionServiceProvider.php b/src/Providers/ExtensionServiceProvider.php index 9a6d35d878..762be5c0e8 100644 --- a/src/Providers/ExtensionServiceProvider.php +++ b/src/Providers/ExtensionServiceProvider.php @@ -70,6 +70,7 @@ class ExtensionServiceProvider extends ServiceProvider Fieldtypes\Color::class, Fieldtypes\Date::class, Fieldtypes\Dictionary::class, + Fieldtypes\DictionaryFields::class, Fieldtypes\Entries::class, Fieldtypes\FieldDisplay::class, Fieldtypes\Files::class, From 040912ef6eba9fa5306752f4773a9c6df2181f3e Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Tue, 2 Jul 2024 10:46:32 +0100 Subject: [PATCH 14/76] wip --- src/Providers/ExtensionServiceProvider.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Providers/ExtensionServiceProvider.php b/src/Providers/ExtensionServiceProvider.php index 762be5c0e8..73a3ee4c94 100644 --- a/src/Providers/ExtensionServiceProvider.php +++ b/src/Providers/ExtensionServiceProvider.php @@ -6,6 +6,7 @@ use Illuminate\Support\ServiceProvider; use Statamic\Actions; use Statamic\Actions\Action; +use Statamic\Dictionaries; use Statamic\Dictionaries\Dictionary; use Statamic\Extend\Manifest; use Statamic\Fields\Fieldtype; @@ -50,8 +51,8 @@ class ExtensionServiceProvider extends ServiceProvider ]; protected $dictionaries = [ - \Statamic\Dictionaries\Countries::class, - \Statamic\Dictionaries\Timezones::class, + Dictionaries\Countries::class, + Dictionaries\Timezones::class, ]; protected $fieldtypes = [ @@ -274,7 +275,7 @@ protected function registerExtensions() 'dictionaries' => [ 'class' => Dictionary::class, 'directory' => 'Dictionaries', - 'extensions' => $this->dictionaries + 'extensions' => $this->dictionaries, ], 'fieldtypes' => [ 'class' => Fieldtype::class, From 482bf42bd8402bbd83f4839811dbd787a0e8493f Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Tue, 2 Jul 2024 11:03:09 +0100 Subject: [PATCH 15/76] wip --- src/Dictionaries/Timezones.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Dictionaries/Timezones.php b/src/Dictionaries/Timezones.php index b8351312c2..e99c67450f 100644 --- a/src/Dictionaries/Timezones.php +++ b/src/Dictionaries/Timezones.php @@ -12,8 +12,8 @@ public function options(?string $search = null): array ->all(); } - public function get(string $option): string|array + public function get(string $key): string|array { - return $option; + return $key; } } From 082cec384c1b7239bfeceb759eaa78b7f779d9b6 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Tue, 2 Jul 2024 11:03:21 +0100 Subject: [PATCH 16/76] Add `make:dictionary` command --- src/Console/Commands/MakeDictionary.php | 74 +++++++++++++++++++ .../Commands/stubs/dictionary.php.stub | 46 ++++++++++++ src/Providers/ConsoleServiceProvider.php | 1 + 3 files changed, 121 insertions(+) create mode 100644 src/Console/Commands/MakeDictionary.php create mode 100644 src/Console/Commands/stubs/dictionary.php.stub diff --git a/src/Console/Commands/MakeDictionary.php b/src/Console/Commands/MakeDictionary.php new file mode 100644 index 0000000000..f01201db3a --- /dev/null +++ b/src/Console/Commands/MakeDictionary.php @@ -0,0 +1,74 @@ +argument('addon')) { + $this->updateServiceProvider(); + } + } + + /** + * Update the Service Provider to register dictionary components. + */ + protected function updateServiceProvider() + { + $factory = new BuilderFactory(); + + $dictionaryClassValue = $factory->classConstFetch('Dictionaries\\'.$this->getNameInput(), 'class'); + + try { + PHPFile::load("addons/{$this->package}/src/ServiceProvider.php") + ->add()->protected()->property('dictionaries', $dictionaryClassValue) + ->save(); + } catch (\Exception $e) { + $this->comment("Don't forget to register the Dictionary class and scripts in your addon's service provider."); + } + } +} diff --git a/src/Console/Commands/stubs/dictionary.php.stub b/src/Console/Commands/stubs/dictionary.php.stub new file mode 100644 index 0000000000..1857928fac --- /dev/null +++ b/src/Console/Commands/stubs/dictionary.php.stub @@ -0,0 +1,46 @@ +getItems() + ->when($search ?? false, function ($collection) use ($search) { + return $collection->filter(fn ($item) => str_contains($item['name'], $search)); + }) + ->mapWithKeys(fn ($item) => [$item['slug'] => $item['name']]) + ->all(); + } + + /** + * Returns a single option. + * + * @param string $key + * @return string|array + */ + public function get(string $key): string|array + { + return $this->getItems()->firstWhere('slug', $key); + } + + private function getItems(): Collection + { + return collect([ + ['name' => 'January', 'slug' => 'january'], + ['name' => 'February', 'slug' => 'february'], + ['name' => 'March', 'slug' => 'march'], + // ... + ]); + } +} diff --git a/src/Providers/ConsoleServiceProvider.php b/src/Providers/ConsoleServiceProvider.php index 79f5bb4517..618a82358b 100644 --- a/src/Providers/ConsoleServiceProvider.php +++ b/src/Providers/ConsoleServiceProvider.php @@ -22,6 +22,7 @@ class ConsoleServiceProvider extends ServiceProvider Commands\LicenseSet::class, Commands\MakeAction::class, Commands\MakeAddon::class, + Commands\MakeDictionary::class, Commands\MakeFieldtype::class, Commands\MakeModifier::class, Commands\MakeScope::class, From 6583ff81df62623d3d333db5efffb6a9bcc6e355 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Tue, 2 Jul 2024 11:08:14 +0100 Subject: [PATCH 17/76] Make these comments align with the comments in the stub --- src/Dictionaries/Dictionary.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Dictionaries/Dictionary.php b/src/Dictionaries/Dictionary.php index ce2b95f46c..3155f561b1 100644 --- a/src/Dictionaries/Dictionary.php +++ b/src/Dictionaries/Dictionary.php @@ -17,12 +17,18 @@ abstract class Dictionary protected $context = []; /** - * Returns all options, optionally filtered by a search term. + * Returns a key/value array of options. + * + * @param string|null $search + * @return array */ abstract public function options(?string $search = null): array; /** - * Returns data for a single option, given the option's key. + * Returns a single option. + * + * @param string $key + * @return string|array */ abstract public function get(string $key): string|array; From ce4d3c7ee738c0c248d355b8becc752678249ece Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Tue, 2 Jul 2024 11:23:43 +0100 Subject: [PATCH 18/76] simplify --- src/Dictionaries/Countries.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Dictionaries/Countries.php b/src/Dictionaries/Countries.php index 0f6559257a..5a20c1bf9d 100644 --- a/src/Dictionaries/Countries.php +++ b/src/Dictionaries/Countries.php @@ -26,7 +26,7 @@ public function options(?string $search = null): array public function get(string $key): string|array { - return $this->getCountries()->filter(fn (array $country) => $country['iso3'] === $key)->first(); + return $this->getCountries()->firstWhere('iso3', $key); } protected function fieldItems() From 3578bf40cb3d891878cce38d050974783e1e8537 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Tue, 2 Jul 2024 11:26:33 +0100 Subject: [PATCH 19/76] Add currencies dictionary --- resources/dictionaries/currencies.json | 1 + src/Dictionaries/Currencies.php | 34 ++++++++++++++++++++++ src/Providers/ExtensionServiceProvider.php | 1 + 3 files changed, 36 insertions(+) create mode 100644 resources/dictionaries/currencies.json create mode 100644 src/Dictionaries/Currencies.php diff --git a/resources/dictionaries/currencies.json b/resources/dictionaries/currencies.json new file mode 100644 index 0000000000..317457f03e --- /dev/null +++ b/resources/dictionaries/currencies.json @@ -0,0 +1 @@ +[{"code":"AED","name":"United Arab Emirates Dirham","symbol":"\u062f.\u0625.\u200f","decimal_digits":2},{"code":"AFN","name":"Afghan Afghani","symbol":"\u060b","decimal_digits":0},{"code":"ALL","name":"Albanian Lek","symbol":"Lek","decimal_digits":0},{"code":"AMD","name":"Armenian Dram","symbol":"\u0564\u0580.","decimal_digits":0},{"code":"ARS","name":"Argentine Peso","symbol":"$","decimal_digits":2},{"code":"AUD","name":"Australian Dollar","symbol":"$","decimal_digits":2},{"code":"AZN","name":"Azerbaijani Manat","symbol":"\u043c\u0430\u043d.","decimal_digits":2},{"code":"BAM","name":"Bosnia-Herzegovina Convertible Mark","symbol":"KM","decimal_digits":2},{"code":"BDT","name":"Bangladeshi Taka","symbol":"\u09f3","decimal_digits":2},{"code":"BGN","name":"Bulgarian Lev","symbol":"\u043b\u0432.","decimal_digits":2},{"code":"BHD","name":"Bahraini Dinar","symbol":"\u062f.\u0628.\u200f","decimal_digits":3},{"code":"BIF","name":"Burundian Franc","symbol":"FBu","decimal_digits":0},{"code":"BND","name":"Brunei Dollar","symbol":"$","decimal_digits":2},{"code":"BOB","name":"Bolivian Boliviano","symbol":"Bs","decimal_digits":2},{"code":"BRL","name":"Brazilian Real","symbol":"R$","decimal_digits":2},{"code":"BWP","name":"Botswanan Pula","symbol":"P","decimal_digits":2},{"code":"BYN","name":"Belarusian Ruble","symbol":"\u0440\u0443\u0431.","decimal_digits":2},{"code":"BZD","name":"Belize Dollar","symbol":"$","decimal_digits":2},{"code":"CAD","name":"Canadian Dollar","symbol":"$","decimal_digits":2},{"code":"CDF","name":"Congolese Franc","symbol":"FrCD","decimal_digits":2},{"code":"CHF","name":"Swiss Franc","symbol":"CHF","decimal_digits":2},{"code":"CLP","name":"Chilean Peso","symbol":"$","decimal_digits":0},{"code":"CNY","name":"Chinese Yuan","symbol":"CN\u00a5","decimal_digits":2},{"code":"COP","name":"Colombian Peso","symbol":"$","decimal_digits":0},{"code":"CRC","name":"Costa Rican Col\u00f3n","symbol":"\u20a1","decimal_digits":0},{"code":"CVE","name":"Cape Verdean Escudo","symbol":"CV$","decimal_digits":2},{"code":"CZK","name":"Czech Republic Koruna","symbol":"K\u010d","decimal_digits":2},{"code":"DJF","name":"Djiboutian Franc","symbol":"Fdj","decimal_digits":0},{"code":"DKK","name":"Danish Krone","symbol":"kr","decimal_digits":2},{"code":"DOP","name":"Dominican Peso","symbol":"RD$","decimal_digits":2},{"code":"DZD","name":"Algerian Dinar","symbol":"\u062f.\u062c.\u200f","decimal_digits":2},{"code":"EEK","name":"Estonian Kroon","symbol":"kr","decimal_digits":2},{"code":"EGP","name":"Egyptian Pound","symbol":"\u062c.\u0645.\u200f","decimal_digits":2},{"code":"ERN","name":"Eritrean Nakfa","symbol":"Nfk","decimal_digits":2},{"code":"ETB","name":"Ethiopian Birr","symbol":"Br","decimal_digits":2},{"code":"EUR","name":"Euro","symbol":"\u20ac","decimal_digits":2},{"code":"GBP","name":"British Pound Sterling","symbol":"\u00a3","decimal_digits":2},{"code":"GEL","name":"Georgian Lari","symbol":"GEL","decimal_digits":2},{"code":"GHS","name":"Ghanaian Cedi","symbol":"GH\u20b5","decimal_digits":2},{"code":"GNF","name":"Guinean Franc","symbol":"FG","decimal_digits":0},{"code":"GTQ","name":"Guatemalan Quetzal","symbol":"Q","decimal_digits":2},{"code":"HKD","name":"Hong Kong Dollar","symbol":"$","decimal_digits":2},{"code":"HNL","name":"Honduran Lempira","symbol":"L","decimal_digits":2},{"code":"HRK","name":"Croatian Kuna","symbol":"kn","decimal_digits":2},{"code":"HUF","name":"Hungarian Forint","symbol":"Ft","decimal_digits":0},{"code":"IDR","name":"Indonesian Rupiah","symbol":"Rp","decimal_digits":0},{"code":"ILS","name":"Israeli New Sheqel","symbol":"\u20aa","decimal_digits":2},{"code":"INR","name":"Indian Rupee","symbol":"\u099f\u0995\u09be","decimal_digits":2},{"code":"IQD","name":"Iraqi Dinar","symbol":"\u062f.\u0639.\u200f","decimal_digits":0},{"code":"IRR","name":"Iranian Rial","symbol":"\ufdfc","decimal_digits":0},{"code":"ISK","name":"Icelandic Kr\u00f3na","symbol":"kr","decimal_digits":0},{"code":"JMD","name":"Jamaican Dollar","symbol":"$","decimal_digits":2},{"code":"JOD","name":"Jordanian Dinar","symbol":"\u062f.\u0623.\u200f","decimal_digits":3},{"code":"JPY","name":"Japanese Yen","symbol":"\uffe5","decimal_digits":0},{"code":"KES","name":"Kenyan Shilling","symbol":"Ksh","decimal_digits":2},{"code":"KHR","name":"Cambodian Riel","symbol":"\u17db","decimal_digits":2},{"code":"KMF","name":"Comorian Franc","symbol":"FC","decimal_digits":0},{"code":"KRW","name":"South Korean Won","symbol":"\u20a9","decimal_digits":0},{"code":"KWD","name":"Kuwaiti Dinar","symbol":"\u062f.\u0643.\u200f","decimal_digits":3},{"code":"KZT","name":"Kazakhstani Tenge","symbol":"\u0442\u04a3\u0433.","decimal_digits":2},{"code":"LBP","name":"Lebanese Pound","symbol":"\u0644.\u0644.\u200f","decimal_digits":0},{"code":"LKR","name":"Sri Lankan Rupee","symbol":"SL Re","decimal_digits":2},{"code":"LTL","name":"Lithuanian Litas","symbol":"Lt","decimal_digits":2},{"code":"LVL","name":"Latvian Lats","symbol":"Ls","decimal_digits":2},{"code":"LYD","name":"Libyan Dinar","symbol":"\u062f.\u0644.\u200f","decimal_digits":3},{"code":"MAD","name":"Moroccan Dirham","symbol":"\u062f.\u0645.\u200f","decimal_digits":2},{"code":"MDL","name":"Moldovan Leu","symbol":"MDL","decimal_digits":2},{"code":"MGA","name":"Malagasy Ariary","symbol":"MGA","decimal_digits":0},{"code":"MKD","name":"Macedonian Denar","symbol":"MKD","decimal_digits":2},{"code":"MMK","name":"Myanma Kyat","symbol":"K","decimal_digits":0},{"code":"MOP","name":"Macanese Pataca","symbol":"MOP$","decimal_digits":2},{"code":"MUR","name":"Mauritian Rupee","symbol":"MURs","decimal_digits":0},{"code":"MXN","name":"Mexican Peso","symbol":"$","decimal_digits":2},{"code":"MYR","name":"Malaysian Ringgit","symbol":"RM","decimal_digits":2},{"code":"MZN","name":"Mozambican Metical","symbol":"MTn","decimal_digits":2},{"code":"NAD","name":"Namibian Dollar","symbol":"N$","decimal_digits":2},{"code":"NGN","name":"Nigerian Naira","symbol":"\u20a6","decimal_digits":2},{"code":"NIO","name":"Nicaraguan C\u00f3rdoba","symbol":"C$","decimal_digits":2},{"code":"NOK","name":"Norwegian Krone","symbol":"kr","decimal_digits":2},{"code":"NPR","name":"Nepalese Rupee","symbol":"\u0928\u0947\u0930\u0942","decimal_digits":2},{"code":"NZD","name":"New Zealand Dollar","symbol":"$","decimal_digits":2},{"code":"OMR","name":"Omani Rial","symbol":"\u0631.\u0639.\u200f","decimal_digits":3},{"code":"PAB","name":"Panamanian Balboa","symbol":"B\/.","decimal_digits":2},{"code":"PEN","name":"Peruvian Nuevo Sol","symbol":"S\/.","decimal_digits":2},{"code":"PHP","name":"Philippine Peso","symbol":"\u20b1","decimal_digits":2},{"code":"PKR","name":"Pakistani Rupee","symbol":"\u20a8","decimal_digits":0},{"code":"PLN","name":"Polish Zloty","symbol":"z\u0142","decimal_digits":2},{"code":"PYG","name":"Paraguayan Guarani","symbol":"\u20b2","decimal_digits":0},{"code":"QAR","name":"Qatari Rial","symbol":"\u0631.\u0642.\u200f","decimal_digits":2},{"code":"RON","name":"Romanian Leu","symbol":"RON","decimal_digits":2},{"code":"RSD","name":"Serbian Dinar","symbol":"\u0434\u0438\u043d.","decimal_digits":0},{"code":"RUB","name":"Russian Ruble","symbol":"\u20bd.","decimal_digits":2},{"code":"RWF","name":"Rwandan Franc","symbol":"FR","decimal_digits":0},{"code":"SAR","name":"Saudi Riyal","symbol":"\u0631.\u0633.\u200f","decimal_digits":2},{"code":"SDG","name":"Sudanese Pound","symbol":"SDG","decimal_digits":2},{"code":"SEK","name":"Swedish Krona","symbol":"kr","decimal_digits":2},{"code":"SGD","name":"Singapore Dollar","symbol":"$","decimal_digits":2},{"code":"SOS","name":"Somali Shilling","symbol":"Ssh","decimal_digits":0},{"code":"SYP","name":"Syrian Pound","symbol":"\u0644.\u0633.\u200f","decimal_digits":0},{"code":"THB","name":"Thai Baht","symbol":"\u0e3f","decimal_digits":2},{"code":"TND","name":"Tunisian Dinar","symbol":"\u062f.\u062a.\u200f","decimal_digits":3},{"code":"TOP","name":"Tongan Pa\u02bbanga","symbol":"T$","decimal_digits":2},{"code":"TRY","name":"Turkish Lira","symbol":"TL","decimal_digits":2},{"code":"TTD","name":"Trinidad and Tobago Dollar","symbol":"$","decimal_digits":2},{"code":"TWD","name":"New Taiwan Dollar","symbol":"NT$","decimal_digits":2},{"code":"TZS","name":"Tanzanian Shilling","symbol":"TSh","decimal_digits":0},{"code":"UAH","name":"Ukrainian Hryvnia","symbol":"\u20b4","decimal_digits":2},{"code":"UGX","name":"Ugandan Shilling","symbol":"USh","decimal_digits":0},{"code":"USD","name":"US Dollar","symbol":"$","decimal_digits":2},{"code":"UYU","name":"Uruguayan Peso","symbol":"$","decimal_digits":2},{"code":"UZS","name":"Uzbekistan Som","symbol":"UZS","decimal_digits":0},{"code":"VEF","name":"Venezuelan Bol\u00edvar","symbol":"Bs.F.","decimal_digits":2},{"code":"VND","name":"Vietnamese Dong","symbol":"\u20ab","decimal_digits":0},{"code":"XAF","name":"CFA Franc BEAC","symbol":"FCFA","decimal_digits":0},{"code":"XOF","name":"CFA Franc BCEAO","symbol":"CFA","decimal_digits":0},{"code":"YER","name":"Yemeni Rial","symbol":"\u0631.\u064a.\u200f","decimal_digits":0},{"code":"ZAR","name":"South African Rand","symbol":"R","decimal_digits":2},{"code":"ZMK","name":"Zambian Kwacha","symbol":"ZK","decimal_digits":0},{"code":"ZWL","name":"Zimbabwean Dollar","symbol":"ZWL$","decimal_digits":0}] \ No newline at end of file diff --git a/src/Dictionaries/Currencies.php b/src/Dictionaries/Currencies.php new file mode 100644 index 0000000000..c31ff4557b --- /dev/null +++ b/src/Dictionaries/Currencies.php @@ -0,0 +1,34 @@ +getCurrencies() + ->when($search ?? false, function ($collection) use ($search) { + return $collection->filter(function (array $currency) use ($search) { + return str_contains(strtolower($currency['name']), strtolower($search)) + || str_contains(strtolower($currency['code']), strtolower($search)); + }); + }) + ->mapWithKeys(function (array $currency) { + return [$currency['code'] => "{$currency['name']} ({$currency['code']})"]; + }) + ->all(); + } + + public function get(string $key): string|array + { + return $this->getCurrencies()->firstWhere('code', $key); + } + + private function getCurrencies(): Collection + { + return collect(json_decode(File::get(__DIR__.'/../../resources/dictionaries/currencies.json'), true)); + } +} diff --git a/src/Providers/ExtensionServiceProvider.php b/src/Providers/ExtensionServiceProvider.php index 73a3ee4c94..30de3f4d76 100644 --- a/src/Providers/ExtensionServiceProvider.php +++ b/src/Providers/ExtensionServiceProvider.php @@ -52,6 +52,7 @@ class ExtensionServiceProvider extends ServiceProvider protected $dictionaries = [ Dictionaries\Countries::class, + Dictionaries\Currencies::class, Dictionaries\Timezones::class, ]; From 0641a346971997684c157d192dd881cdb8b1f984 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Tue, 2 Jul 2024 11:41:28 +0100 Subject: [PATCH 20/76] Undo changes by PHPStorm's refactoring feature --- tests/Antlers/Runtime/ArraysTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Antlers/Runtime/ArraysTest.php b/tests/Antlers/Runtime/ArraysTest.php index a6f95d7be4..cb22b7d8bb 100644 --- a/tests/Antlers/Runtime/ArraysTest.php +++ b/tests/Antlers/Runtime/ArraysTest.php @@ -26,13 +26,13 @@ public function test_dictionary_access() 'address' => [ 'city' => 'City Name', 'region' => 'Region Name', - 'country' => 'Countries Name', + 'country' => 'Country Name', ], ]; $this->assertSame('City Name', $this->renderString('{{ address:city }}', $data)); $this->assertSame('Region Name', $this->renderString('{{ address:region }}', $data)); - $this->assertSame('Countries Name', $this->renderString('{{ address:country }}', $data)); + $this->assertSame('Country Name', $this->renderString('{{ address:country }}', $data)); } public function test_multi_dimensional_array_access() From 0407218804ba7ebe98a6603d602fe475bf1c4327 Mon Sep 17 00:00:00 2001 From: duncanmcclean Date: Tue, 2 Jul 2024 10:48:08 +0000 Subject: [PATCH 21/76] Fix styling --- src/Dictionaries/Dictionary.php | 6 ------ src/Facades/Dictionary.php | 1 - src/Fieldtypes/DictionaryFields.php | 2 +- 3 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/Dictionaries/Dictionary.php b/src/Dictionaries/Dictionary.php index 3155f561b1..f4688d7c23 100644 --- a/src/Dictionaries/Dictionary.php +++ b/src/Dictionaries/Dictionary.php @@ -18,17 +18,11 @@ abstract class Dictionary /** * Returns a key/value array of options. - * - * @param string|null $search - * @return array */ abstract public function options(?string $search = null): array; /** * Returns a single option. - * - * @param string $key - * @return string|array */ abstract public function get(string $key): string|array; diff --git a/src/Facades/Dictionary.php b/src/Facades/Dictionary.php index 752b41cb54..7b8fe79ddc 100644 --- a/src/Facades/Dictionary.php +++ b/src/Facades/Dictionary.php @@ -3,7 +3,6 @@ namespace Statamic\Facades; use Illuminate\Support\Facades\Facade; -use Statamic\Actions\ActionRepository; use Statamic\Dictionaries\DictionaryRepository; /** diff --git a/src/Fieldtypes/DictionaryFields.php b/src/Fieldtypes/DictionaryFields.php index e3d303477c..607276d9fb 100644 --- a/src/Fieldtypes/DictionaryFields.php +++ b/src/Fieldtypes/DictionaryFields.php @@ -2,9 +2,9 @@ namespace Statamic\Fieldtypes; +use Statamic\Facades\Dictionary; use Statamic\Fields\Fields; use Statamic\Fields\Fieldtype; -use Statamic\Facades\Dictionary; use Statamic\Support\Arr; class DictionaryFields extends Fieldtype From 11e0fd2aa9221cbaf028911b64845730e33a0b6d Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Tue, 2 Jul 2024 11:48:59 +0100 Subject: [PATCH 22/76] wip --- tests/Antlers/Runtime/ArraysTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Antlers/Runtime/ArraysTest.php b/tests/Antlers/Runtime/ArraysTest.php index cb22b7d8bb..04d1cbd345 100644 --- a/tests/Antlers/Runtime/ArraysTest.php +++ b/tests/Antlers/Runtime/ArraysTest.php @@ -408,9 +408,9 @@ public function test_creation_of_new_array_elements() Two: Two Three: Three -One: -Two: -Three: +One: +Two: +Three: From 8059e04e0d29d2e333521eb73fb2e0c522475e71 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Tue, 2 Jul 2024 11:50:03 +0100 Subject: [PATCH 23/76] remove unrelated change --- src/Tags/Collection/Entries.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tags/Collection/Entries.php b/src/Tags/Collection/Entries.php index e11ef1c762..dc910752d6 100644 --- a/src/Tags/Collection/Entries.php +++ b/src/Tags/Collection/Entries.php @@ -190,8 +190,8 @@ protected function query() $this->queryTaxonomies($query); $this->queryRedirects($query); $this->queryConditions($query); - $this->queryOrderBys($query); $this->queryScopes($query); + $this->queryOrderBys($query); return $query; } From 71601ed3a65bdf46663aba07af9ddaf903cade32 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Tue, 2 Jul 2024 11:56:03 +0100 Subject: [PATCH 24/76] dictionaries don't have scripts --- src/Console/Commands/MakeDictionary.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Console/Commands/MakeDictionary.php b/src/Console/Commands/MakeDictionary.php index f01201db3a..f1a8618496 100644 --- a/src/Console/Commands/MakeDictionary.php +++ b/src/Console/Commands/MakeDictionary.php @@ -68,7 +68,7 @@ protected function updateServiceProvider() ->add()->protected()->property('dictionaries', $dictionaryClassValue) ->save(); } catch (\Exception $e) { - $this->comment("Don't forget to register the Dictionary class and scripts in your addon's service provider."); + $this->comment("Don't forget to register the Dictionary class in your addon's service provider."); } } } From 540e07ac0cf304c093f244720c33516160016152 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Tue, 2 Jul 2024 11:56:11 +0100 Subject: [PATCH 25/76] rename variable in facade docblock --- src/Facades/Dictionary.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Facades/Dictionary.php b/src/Facades/Dictionary.php index 7b8fe79ddc..b4e655eb5c 100644 --- a/src/Facades/Dictionary.php +++ b/src/Facades/Dictionary.php @@ -7,7 +7,7 @@ /** * @method static \Illuminate\Support\Collection all() - * @method static \Statamic\Dictionaries\Dictionary find(string $handle, array $dictionary = []) + * @method static \Statamic\Dictionaries\Dictionary find(string $handle, array $context = []) * * @see \Statamic\Actions\DictionaryRepository */ From f2aadc188586a13540b170968644c65a43bc7b5d Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Tue, 2 Jul 2024 11:57:12 +0100 Subject: [PATCH 26/76] it's on my to-do list - remove the todo --- src/Fieldtypes/Dictionary.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Fieldtypes/Dictionary.php b/src/Fieldtypes/Dictionary.php index 24f3ebb03f..51dc64cfd9 100644 --- a/src/Fieldtypes/Dictionary.php +++ b/src/Fieldtypes/Dictionary.php @@ -135,6 +135,4 @@ public function dictionary(): \Statamic\Dictionaries\Dictionary return $dictionary; } - - // TODO: graphql - how can we make it work since the keys will be dynamic? } From 85a1452a862700ad63155d1887e40caa2a0183b8 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Tue, 2 Jul 2024 18:40:46 +0100 Subject: [PATCH 27/76] Give the fieldtype a proper icon --- resources/svg/icons/light/dictionary.svg | 1 + src/Fieldtypes/Dictionary.php | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 resources/svg/icons/light/dictionary.svg diff --git a/resources/svg/icons/light/dictionary.svg b/resources/svg/icons/light/dictionary.svg new file mode 100644 index 0000000000..4147382376 --- /dev/null +++ b/resources/svg/icons/light/dictionary.svg @@ -0,0 +1 @@ + diff --git a/src/Fieldtypes/Dictionary.php b/src/Fieldtypes/Dictionary.php index 51dc64cfd9..09bcaf7de3 100644 --- a/src/Fieldtypes/Dictionary.php +++ b/src/Fieldtypes/Dictionary.php @@ -12,7 +12,6 @@ class Dictionary extends Fieldtype protected $categories = ['controls', 'relationship']; protected $selectableInForms = true; protected $indexComponent = 'tags'; - protected $icon = 'select'; protected function configFieldItems(): array { From 00a770175b3d2404010da4ee378b5db40dafc1c2 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Tue, 2 Jul 2024 19:01:01 +0100 Subject: [PATCH 28/76] wip --- src/Fieldtypes/DictionaryFields.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Fieldtypes/DictionaryFields.php b/src/Fieldtypes/DictionaryFields.php index 607276d9fb..35cac097bc 100644 --- a/src/Fieldtypes/DictionaryFields.php +++ b/src/Fieldtypes/DictionaryFields.php @@ -78,7 +78,7 @@ public function extraRules(): array { if (! $dictionary = Arr::get($this->field->value(), 'type')) { return [ - $this->field->handle().'.type' => 'required', + $this->field->handle().'.type' => ['required'], ]; } From a7f0ebd53ead813250bd565712296f92afde4373 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Tue, 2 Jul 2024 19:01:09 +0100 Subject: [PATCH 29/76] Backfill tests --- .../Concerns/CleansUpGeneratedPaths.php | 1 + tests/Console/Commands/MakeDictionaryTest.php | 92 +++++++++++ tests/Dictionaries/DictionariesTest.php | 129 +++++++++++++++ tests/Fieldtypes/DictionariesTest.php | 106 ++++++++++++ tests/Fieldtypes/DictionaryFieldsTest.php | 155 ++++++++++++++++++ 5 files changed, 483 insertions(+) create mode 100644 tests/Console/Commands/MakeDictionaryTest.php create mode 100644 tests/Dictionaries/DictionariesTest.php create mode 100644 tests/Fieldtypes/DictionariesTest.php create mode 100644 tests/Fieldtypes/DictionaryFieldsTest.php diff --git a/tests/Console/Commands/Concerns/CleansUpGeneratedPaths.php b/tests/Console/Commands/Concerns/CleansUpGeneratedPaths.php index c72fb1dcc3..45d2a63ed4 100644 --- a/tests/Console/Commands/Concerns/CleansUpGeneratedPaths.php +++ b/tests/Console/Commands/Concerns/CleansUpGeneratedPaths.php @@ -11,6 +11,7 @@ protected function cleanupPaths() $dirs = [ base_path('addons'), base_path('app/Actions'), + base_path('app/Dictionaries'), base_path('app/Fieldtypes'), base_path('app/Modifiers'), base_path('app/Scopes'), diff --git a/tests/Console/Commands/MakeDictionaryTest.php b/tests/Console/Commands/MakeDictionaryTest.php new file mode 100644 index 0000000000..48c9a61832 --- /dev/null +++ b/tests/Console/Commands/MakeDictionaryTest.php @@ -0,0 +1,92 @@ +files = app(Filesystem::class); + $this->fakeSuccessfulComposerRequire(); + } + + public function tearDown(): void + { + $this->cleanupPaths(); + + parent::tearDown(); + } + + #[Test] + public function it_can_make_a_dictionary() + { + $path = base_path('app/Dictionaries/Provinces.php'); + + $this->assertFileDoesNotExist($path); + + $this->artisan('statamic:make:dictionary', ['name' => 'Provinces']); + + $this->assertFileExists($path); + $this->assertStringContainsString('namespace App\Dictionaries;', $this->files->get($path)); + } + + #[Test] + public function it_will_not_overwrite_an_existing_dictionary() + { + $path = base_path('app/Dictionaries/Provinces.php'); + + $this->artisan('statamic:make:dictionary', ['name' => 'Provinces']); + $this->files->put($path, 'overwritten action'); + + $this->assertStringContainsString('overwritten action', $this->files->get($path)); + + $this->artisan('statamic:make:dictionary', ['name' => 'Provinces']); + + $this->assertStringContainsString('overwritten action', $this->files->get($path)); + } + + #[Test] + public function using_force_option_will_overwrite_original_dictionary() + { + $path = base_path('app/Dictionaries/Provinces.php'); + + $this->artisan('statamic:make:dictionary', ['name' => 'Provinces']); + $this->files->put($path, 'overwritten action'); + + $this->assertStringContainsString('overwritten action', $this->files->get($path)); + + $this->artisan('statamic:make:dictionary', ['name' => 'Provinces', '--force' => true]); + + $this->assertStringNotContainsString('overwritten action', $this->files->get($path)); + } + + #[Test] + public function it_can_make_a_dictionary_into_an_addon() + { + $path = base_path('addons/yoda/bag-odah'); + + $this->artisan('statamic:make:addon', ['addon' => 'yoda/bag-odah']); + + Composer::shouldReceive('installedPath')->andReturn($path); + + $this->assertFileDoesNotExist($action = "$path/src/Dictionaries/Provinces.php"); + + $this->artisan('statamic:make:dictionary', ['name' => 'Provinces', 'addon' => 'yoda/bag-odah']); + + $this->assertFileExists($action); + $this->assertStringContainsString('namespace Yoda\BagOdah\Dictionaries;', $this->files->get($action)); + } +} diff --git a/tests/Dictionaries/DictionariesTest.php b/tests/Dictionaries/DictionariesTest.php new file mode 100644 index 0000000000..162c0faa4c --- /dev/null +++ b/tests/Dictionaries/DictionariesTest.php @@ -0,0 +1,129 @@ +assertCount(4, $all); // The built-in dictionaries + our fake one + $this->assertEveryItem($all, fn ($item) => $item instanceof Dictionary); + } + + #[Test] + public function can_get_a_dictionary() + { + $find = DictionaryFacade::find('fake_dictionary'); + + $this->assertInstanceOf(Dictionary::class, $find); + $this->assertSame('fake_dictionary', $find->handle()); + } + + #[Test] + public function can_get_options() + { + $dictionary = DictionaryFacade::find('fake_dictionary'); + + $this->assertEquals([ + 'foo' => 'Foo', + 'bar' => 'Bar', + 'baz' => 'Baz', + 'qux' => 'Qux', + ], $dictionary->options()); + } + + #[Test] + public function can_get_options_with_search_query() + { + $dictionary = DictionaryFacade::find('fake_dictionary'); + + $this->assertEquals([ + 'bar' => 'Bar', + 'baz' => 'Baz', + ], $dictionary->options('ba')); + } + + #[Test] + public function can_get_option() + { + $dictionary = DictionaryFacade::find('fake_dictionary'); + + $this->assertEquals([ + 'name' => 'Foo', + 'id' => 'foo', + ], $dictionary->get('foo')); + } + + #[Test] + public function ensure_context_is_passed_to_dictionary() + { + $dictionary = DictionaryFacade::find('fake_dictionary', [ + 'sort_in_alphabetical_order' => true, + ]); + + // When the sort_in_alphabetical_order context is passed, + // the options should be returned in alphabetical order. + $this->assertEquals([ + 'bar' => 'Bar', + 'baz' => 'Baz', + 'foo' => 'Foo', + 'qux' => 'Qux', + ], $dictionary->options()); + } +} + +class FakeDictionary extends Dictionary +{ + public function options(?string $search = null): array + { + return $this->data() + ->when($search ?? false, function ($collection) use ($search) { + return $collection->filter(fn ($item) => str_contains($item['id'], $search)); + }) + ->mapWithKeys(fn ($item) => [$item['id'] => $item['name']]) + ->when($this->context['sort_in_alphabetical_order'] ?? false, function ($collection) { + return $collection->sortBy('id'); + }) + ->all(); + } + + public function get(string $key): string|array + { + return $this->data()->firstWhere('id', $key); + } + + protected function data() + { + return collect([ + ['name' => 'Foo', 'id' => 'foo'], + ['name' => 'Bar', 'id' => 'bar'], + ['name' => 'Baz', 'id' => 'baz'], + ['name' => 'Qux', 'id' => 'qux'], + ]); + } + + protected function fieldItems() + { + return [ + 'sort_in_alphabetical_order' => [ + 'display' => 'Sort in alphabetical order?', + 'type' => 'toggle', + ], + ]; + } +} diff --git a/tests/Fieldtypes/DictionariesTest.php b/tests/Fieldtypes/DictionariesTest.php new file mode 100644 index 0000000000..d818ebdbbe --- /dev/null +++ b/tests/Fieldtypes/DictionariesTest.php @@ -0,0 +1,106 @@ + 'dictionary', 'dictionary' => 'countries'])); + $field->setValue(['USA', 'AUS', 'CAN', 'DEU', 'GBR']); + + $fieldtype = FieldtypeRepository::find('dictionary'); + $fieldtype->setField($field); + + $preload = $fieldtype->preload(); + + $this->assertArraySubset([ + 'url' => 'http://localhost/cp/fieldtypes/dictionaries/countries', + 'selectedOptions' => [ + ['value' => 'AUS', 'label' => '🇦🇺 Australia'], + ['value' => 'CAN', 'label' => '🇨🇦 Canada'], + ['value' => 'DEU', 'label' => '🇩🇪 Germany'], + ['value' => 'GBR', 'label' => '🇬🇧 United Kingdom'], + ['value' => 'USA', 'label' => '🇺🇸 United States'], + ], + ], $preload); + } + + #[Test] + public function it_augments_a_single_option() + { + $field = (new Field('test', ['type' => 'dictionary', 'dictionary' => 'countries'])); + + $fieldtype = FieldtypeRepository::find('dictionary'); + $fieldtype->setField($field); + + $augment = $fieldtype->augment('USA'); + + $this->assertEquals([ + 'name' => 'United States', + 'iso3' => 'USA', + 'iso2' => 'US', + 'region' => 'Americas', + 'subregion' => 'Northern America', + 'emoji' => '🇺🇸', + ], $augment); + } + + #[Test] + public function it_augments_multiple_options() + { + $field = (new Field('test', ['type' => 'dictionary', 'dictionary' => 'countries', 'multiple' => true])); + + $fieldtype = FieldtypeRepository::find('dictionary'); + $fieldtype->setField($field); + + $augment = $fieldtype->augment(['USA', 'GBR']); + + $this->assertEquals([ + [ + 'name' => 'United States', + 'iso3' => 'USA', + 'iso2' => 'US', + 'region' => 'Americas', + 'subregion' => 'Northern America', + 'emoji' => '🇺🇸', + ], + [ + 'name' => 'United Kingdom', + 'iso3' => 'GBR', + 'iso2' => 'GB', + 'region' => 'Europe', + 'subregion' => 'Northern Europe', + 'emoji' => '🇬🇧', + ], + ], $augment); + } + + #[Test] + public function it_returns_extra_renderable_field_data() + { + $field = (new Field('test', ['type' => 'dictionary', 'dictionary' => 'countries'])); + $field->setValue(['USA', 'AUS', 'CAN', 'DEU', 'GBR']); + + $fieldtype = FieldtypeRepository::find('dictionary'); + $fieldtype->setField($field); + + $extraRenderableFieldData = $fieldtype->extraRenderableFieldData(); + + $this->assertArraySubset([ + 'options' => [ + 'AUS' => '🇦🇺 Australia', + 'CAN' => '🇨🇦 Canada', + 'DEU' => '🇩🇪 Germany', + 'GBR' => '🇬🇧 United Kingdom', + 'USA' => '🇺🇸 United States', + ], + ], $extraRenderableFieldData); + } +} diff --git a/tests/Fieldtypes/DictionaryFieldsTest.php b/tests/Fieldtypes/DictionaryFieldsTest.php new file mode 100644 index 0000000000..862c3cbf4a --- /dev/null +++ b/tests/Fieldtypes/DictionaryFieldsTest.php @@ -0,0 +1,155 @@ +preload(); + + $this->assertArraySubset([ + 'type' => [ + 'fields' => [ + ['handle' => 'type', 'type' => 'select'], + ], + 'meta' => collect(['type' => null]), + ], + ], $preload); + + $this->assertArraySubset([ + 'fake_dictionary' => [ + 'fields' => [ + ['handle' => 'category', 'type' => 'select'], + ], + 'meta' => collect(['category' => null]), + 'defaults' => collect(['category' => null]), + ], + ], $preload['dictionaries']->all()); + } + + #[Test] + public function it_pre_processes_dictionary_fields() + { + $fieldtype = FieldtypeRepository::find('dictionary_fields'); + + $preProcess = $fieldtype->preProcess([ + 'type' => 'fake_dictionary', + 'category' => 'foo', + ]); + + $this->assertEquals([ + 'type' => 'fake_dictionary', + 'category' => 'foo', + ], $preProcess); + } + + #[Test] + public function it_pre_processes_dictionary_fields_saved_as_a_string() + { + $fieldtype = FieldtypeRepository::find('dictionary_fields'); + + $preProcess = $fieldtype->preProcess('fake_dictionary'); + + $this->assertEquals([ + 'type' => 'fake_dictionary', + ], $preProcess); + } + + #[Test] + public function it_processes_dictionary_fields() + { + $fieldtype = FieldtypeRepository::find('dictionary_fields'); + + $process = $fieldtype->process([ + 'type' => 'fake_dictionary', + 'category' => 'foo', + ]); + + $this->assertEquals([ + 'type' => 'fake_dictionary', + 'category' => 'foo', + ], $process); + } + + #[Test] + public function it_processes_dictionary_fields_into_a_string_when_dictionary_has_no_config_values() + { + $fieldtype = FieldtypeRepository::find('dictionary_fields'); + + $process = $fieldtype->process([ + 'type' => 'fake_dictionary', + ]); + + $this->assertEquals('fake_dictionary', $process); + } + + #[Test] + public function it_returns_validation_rules() + { + $field = (new Field('test', ['type' => 'dictionary_fields']))->setValue(['type' => 'fake_dictionary']); + + $fieldtype = FieldtypeRepository::find('dictionary_fields'); + $fieldtype->setField($field); + + $extraRules = $fieldtype->extraRules(); + + $this->assertEquals([ + 'test.category' => ['required'], + ], $extraRules); + } + + #[Test] + public function it_returns_validation_rules_when_no_dictionary_is_selected() + { + $field = (new Field('test', ['type' => 'dictionary_fields'])); + + $fieldtype = FieldtypeRepository::find('dictionary_fields'); + $fieldtype->setField($field); + + $extraRules = $fieldtype->extraRules(); + + $this->assertEquals([ + 'test.type' => ['required'], + ], $extraRules); + } +} + +class FakeDictionary extends Dictionary +{ + public function options(?string $search = null): array + { + return []; + } + + public function get(string $key): string|array + { + return []; + } + + protected function fieldItems() + { + return [ + 'category' => [ + 'type' => 'select', + 'validate' => 'required', + ], + ]; + } +} From 52fe753aa0dad92eb8e7701b4e53122134ab2279 Mon Sep 17 00:00:00 2001 From: duncanmcclean Date: Tue, 2 Jul 2024 18:03:07 +0000 Subject: [PATCH 30/76] Fix styling --- tests/Dictionaries/DictionariesTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Dictionaries/DictionariesTest.php b/tests/Dictionaries/DictionariesTest.php index 162c0faa4c..1f5f4add92 100644 --- a/tests/Dictionaries/DictionariesTest.php +++ b/tests/Dictionaries/DictionariesTest.php @@ -4,8 +4,8 @@ use PHPUnit\Framework\Attributes\Test; use Statamic\Dictionaries\Dictionary; -use Tests\TestCase; use Statamic\Facades\Dictionary as DictionaryFacade; +use Tests\TestCase; class DictionariesTest extends TestCase { From 0225a9b3a815c63c264f9949383d9b61b35809ba Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Tue, 23 Jul 2024 16:28:35 -0400 Subject: [PATCH 31/76] words --- resources/lang/en/fieldtypes.php | 2 +- resources/lang/en/messages.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/lang/en/fieldtypes.php b/resources/lang/en/fieldtypes.php index 6d526d6b4b..b4518d459f 100644 --- a/resources/lang/en/fieldtypes.php +++ b/resources/lang/en/fieldtypes.php @@ -72,7 +72,7 @@ 'date.config.time_enabled' => 'Enable the timepicker.', 'date.config.time_seconds_enabled' => 'Show seconds in the timepicker.', 'date.title' => 'Date', - 'dictionary.config.dictionary' => 'Please select the dictionary you wish to pull options from.', + 'dictionary.config.dictionary' => 'The dictionary you wish to pull options from.', 'entries.config.create' => 'Allow creation of new entries.', 'entries.config.collections' => 'Choose which collections the user can select from.', 'entries.config.query_scopes' => 'Choose which query scopes should be applied when retrieving selectable entries.', diff --git a/resources/lang/en/messages.php b/resources/lang/en/messages.php index 36114bdaff..daf1ade7b3 100644 --- a/resources/lang/en/messages.php +++ b/resources/lang/en/messages.php @@ -69,7 +69,7 @@ 'collections_sort_direction_instructions' => 'The default sort direction.', 'collections_preview_target_refresh_instructions' => 'Automatically refresh the preview while editing. Disabling this will use postMessage.', 'collections_taxonomies_instructions' => 'Connect entries in this collection to taxonomies. Fields will be automatically added to publish forms.', - 'dictionaries_countries_region_instructions' => 'Select a region to filter the list of countries.', + 'dictionaries_countries_region_instructions' => 'Optionally filter the countries by region.', 'duplicate_action_warning_localization' => 'This entry is a localization. The origin entry will be duplicated.', 'duplicate_action_warning_localizations' => 'One or more selected entries are localizations. In those cases, the origin entry will be duplicated instead.', 'duplicate_action_localizations_confirmation' => 'Are you sure you want to run this action? Localizations will also be duplicated.', From 12249502598f9d9d3e2c978f511ce647cc76bb5e Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Tue, 23 Jul 2024 16:29:31 -0400 Subject: [PATCH 32/76] assume we always get an array --- src/Dictionaries/Countries.php | 2 +- src/Dictionaries/Currencies.php | 2 +- src/Dictionaries/Dictionary.php | 2 +- src/Dictionaries/Timezones.php | 2 +- tests/Dictionaries/DictionariesTest.php | 2 +- tests/Fieldtypes/DictionaryFieldsTest.php | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Dictionaries/Countries.php b/src/Dictionaries/Countries.php index 5a20c1bf9d..980934cc52 100644 --- a/src/Dictionaries/Countries.php +++ b/src/Dictionaries/Countries.php @@ -24,7 +24,7 @@ public function options(?string $search = null): array ->all(); } - public function get(string $key): string|array + public function get(string $key): array { return $this->getCountries()->firstWhere('iso3', $key); } diff --git a/src/Dictionaries/Currencies.php b/src/Dictionaries/Currencies.php index c31ff4557b..25aca79cdd 100644 --- a/src/Dictionaries/Currencies.php +++ b/src/Dictionaries/Currencies.php @@ -22,7 +22,7 @@ public function options(?string $search = null): array ->all(); } - public function get(string $key): string|array + public function get(string $key): array { return $this->getCurrencies()->firstWhere('code', $key); } diff --git a/src/Dictionaries/Dictionary.php b/src/Dictionaries/Dictionary.php index f4688d7c23..539d4f7c36 100644 --- a/src/Dictionaries/Dictionary.php +++ b/src/Dictionaries/Dictionary.php @@ -24,7 +24,7 @@ abstract public function options(?string $search = null): array; /** * Returns a single option. */ - abstract public function get(string $key): string|array; + abstract public function get(string $key): array; public function context($context) { diff --git a/src/Dictionaries/Timezones.php b/src/Dictionaries/Timezones.php index e99c67450f..e09d015292 100644 --- a/src/Dictionaries/Timezones.php +++ b/src/Dictionaries/Timezones.php @@ -12,7 +12,7 @@ public function options(?string $search = null): array ->all(); } - public function get(string $key): string|array + public function get(string $key): array { return $key; } diff --git a/tests/Dictionaries/DictionariesTest.php b/tests/Dictionaries/DictionariesTest.php index 1f5f4add92..9f31718cc9 100644 --- a/tests/Dictionaries/DictionariesTest.php +++ b/tests/Dictionaries/DictionariesTest.php @@ -102,7 +102,7 @@ public function options(?string $search = null): array ->all(); } - public function get(string $key): string|array + public function get(string $key): array { return $this->data()->firstWhere('id', $key); } diff --git a/tests/Fieldtypes/DictionaryFieldsTest.php b/tests/Fieldtypes/DictionaryFieldsTest.php index 862c3cbf4a..dc63eb576d 100644 --- a/tests/Fieldtypes/DictionaryFieldsTest.php +++ b/tests/Fieldtypes/DictionaryFieldsTest.php @@ -138,7 +138,7 @@ public function options(?string $search = null): array return []; } - public function get(string $key): string|array + public function get(string $key): array { return []; } From 5df5d5763d61174d2aea5982413927d47e40d50b Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Tue, 23 Jul 2024 16:43:57 -0400 Subject: [PATCH 33/76] timezone returns the name and utc offset --- src/Dictionaries/Timezones.php | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/Dictionaries/Timezones.php b/src/Dictionaries/Timezones.php index e09d015292..1817aef334 100644 --- a/src/Dictionaries/Timezones.php +++ b/src/Dictionaries/Timezones.php @@ -2,6 +2,9 @@ namespace Statamic\Dictionaries; +use DateTimeZone; +use Illuminate\Support\Carbon; + class Timezones extends Dictionary { public function options(?string $search = null): array @@ -14,6 +17,19 @@ public function options(?string $search = null): array public function get(string $key): array { - return $key; + return [ + 'name' => $key, + 'offset' => $this->getOffset($key), + ]; + } + + private function getOffset(string $tz): string + { + $tz = new DateTimeZone($tz); + $utcTime = Carbon::now('UTC'); + $offsetInSecs = $tz->getOffset($utcTime); + $hoursAndSec = gmdate('H:i', abs($offsetInSecs)); + + return stripos($offsetInSecs, '-') === false ? "+{$hoursAndSec}" : "-{$hoursAndSec}"; } } From 6147d4c68e8aee2c2bee32f44f3ba2337d5f35cb Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Tue, 23 Jul 2024 16:44:30 -0400 Subject: [PATCH 34/76] initial graphql --- src/Dictionaries/Countries.php | 6 ++ src/Dictionaries/Currencies.php | 6 ++ src/Dictionaries/Dictionary.php | 2 + src/Dictionaries/Timezones.php | 6 ++ src/Fieldtypes/Dictionary.php | 16 ++++- src/GraphQL/Types/CountryDictionaryType.php | 26 +++++++ src/GraphQL/Types/CurrencyDictionaryType.php | 29 ++++++++ src/GraphQL/Types/TimezoneDictionaryType.php | 26 +++++++ .../Fieldtypes/DictionaryFieldtypeTest.php | 68 +++++++++++++++++++ 9 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 src/GraphQL/Types/CountryDictionaryType.php create mode 100644 src/GraphQL/Types/CurrencyDictionaryType.php create mode 100644 src/GraphQL/Types/TimezoneDictionaryType.php create mode 100644 tests/Feature/GraphQL/Fieldtypes/DictionaryFieldtypeTest.php diff --git a/src/Dictionaries/Countries.php b/src/Dictionaries/Countries.php index 980934cc52..00397d783f 100644 --- a/src/Dictionaries/Countries.php +++ b/src/Dictionaries/Countries.php @@ -4,6 +4,7 @@ use Illuminate\Support\Collection; use Statamic\Facades\File; +use Statamic\GraphQL\Types\CountryDictionaryType; class Countries extends Dictionary { @@ -45,4 +46,9 @@ private function getCountries(): Collection { return collect(json_decode(File::get(__DIR__.'/../../resources/dictionaries/countries.json'), true)); } + + public function getGqlType() + { + return new CountryDictionaryType; + } } diff --git a/src/Dictionaries/Currencies.php b/src/Dictionaries/Currencies.php index 25aca79cdd..fe6af739cc 100644 --- a/src/Dictionaries/Currencies.php +++ b/src/Dictionaries/Currencies.php @@ -4,6 +4,7 @@ use Illuminate\Support\Collection; use Statamic\Facades\File; +use Statamic\GraphQL\Types\CurrencyDictionaryType; class Currencies extends Dictionary { @@ -31,4 +32,9 @@ private function getCurrencies(): Collection { return collect(json_decode(File::get(__DIR__.'/../../resources/dictionaries/currencies.json'), true)); } + + public function getGqlType() + { + return new CurrencyDictionaryType; + } } diff --git a/src/Dictionaries/Dictionary.php b/src/Dictionaries/Dictionary.php index 539d4f7c36..a3961e2c7e 100644 --- a/src/Dictionaries/Dictionary.php +++ b/src/Dictionaries/Dictionary.php @@ -37,4 +37,6 @@ protected function fieldItems() { return $this->fields; } + + abstract public function getGqlType(); } diff --git a/src/Dictionaries/Timezones.php b/src/Dictionaries/Timezones.php index 1817aef334..4b87ba0944 100644 --- a/src/Dictionaries/Timezones.php +++ b/src/Dictionaries/Timezones.php @@ -4,6 +4,7 @@ use DateTimeZone; use Illuminate\Support\Carbon; +use Statamic\GraphQL\Types\TimezoneDictionaryType; class Timezones extends Dictionary { @@ -32,4 +33,9 @@ private function getOffset(string $tz): string return stripos($offsetInSecs, '-') === false ? "+{$hoursAndSec}" : "-{$hoursAndSec}"; } + + public function getGqlType() + { + return new TimezoneDictionaryType; + } } diff --git a/src/Fieldtypes/Dictionary.php b/src/Fieldtypes/Dictionary.php index 09bcaf7de3..da8a8df0b6 100644 --- a/src/Fieldtypes/Dictionary.php +++ b/src/Fieldtypes/Dictionary.php @@ -4,6 +4,7 @@ use Statamic\Exceptions\DictionaryNotFoundException; use Statamic\Exceptions\UndefinedDictionaryException; +use Statamic\Facades\GraphQL; use Statamic\Fields\Fieldtype; use Statamic\Support\Arr; @@ -89,7 +90,7 @@ public function preload(): array public function augment($value) { if (is_null($value)) { - return []; + return null; } $dictionary = $this->dictionary(); @@ -134,4 +135,17 @@ public function dictionary(): \Statamic\Dictionaries\Dictionary return $dictionary; } + + public function toGqlType() + { + $type = GraphQL::type($this->dictionary()->getGqlType()->name); + + return $this->multiple() ? GraphQL::listOf($type) : $type; + + } + + public function addGqlTypes() + { + GraphQL::addType($this->dictionary()->getGqlType()); + } } diff --git a/src/GraphQL/Types/CountryDictionaryType.php b/src/GraphQL/Types/CountryDictionaryType.php new file mode 100644 index 0000000000..0d229cffb4 --- /dev/null +++ b/src/GraphQL/Types/CountryDictionaryType.php @@ -0,0 +1,26 @@ + self::NAME, + ]; + + public function fields(): array + { + return [ + 'name' => [ + 'type' => GraphQL::nonNull(GraphQL::string()), + ], + 'iso2' => [ + 'type' => GraphQL::nonNull(GraphQL::string()), + ], + ]; + } +} diff --git a/src/GraphQL/Types/CurrencyDictionaryType.php b/src/GraphQL/Types/CurrencyDictionaryType.php new file mode 100644 index 0000000000..47ff78b5ec --- /dev/null +++ b/src/GraphQL/Types/CurrencyDictionaryType.php @@ -0,0 +1,29 @@ + self::NAME, + ]; + + public function fields(): array + { + return [ + 'name' => [ + 'type' => GraphQL::nonNull(GraphQL::string()), + ], + 'symbol' => [ + 'type' => GraphQL::nonNull(GraphQL::string()), + ], + 'code' => [ + 'type' => GraphQL::nonNull(GraphQL::string()), + ], + ]; + } +} diff --git a/src/GraphQL/Types/TimezoneDictionaryType.php b/src/GraphQL/Types/TimezoneDictionaryType.php new file mode 100644 index 0000000000..4c182815dd --- /dev/null +++ b/src/GraphQL/Types/TimezoneDictionaryType.php @@ -0,0 +1,26 @@ + self::NAME, + ]; + + public function fields(): array + { + return [ + 'name' => [ + 'type' => GraphQL::nonNull(GraphQL::string()), + ], + 'offset' => [ + 'type' => GraphQL::nonNull(GraphQL::string()), + ], + ]; + } +} diff --git a/tests/Feature/GraphQL/Fieldtypes/DictionaryFieldtypeTest.php b/tests/Feature/GraphQL/Fieldtypes/DictionaryFieldtypeTest.php new file mode 100644 index 0000000000..fb027df669 --- /dev/null +++ b/tests/Feature/GraphQL/Fieldtypes/DictionaryFieldtypeTest.php @@ -0,0 +1,68 @@ +createEntryWithFields([ + 'undefined' => [ + 'value' => null, + 'field' => ['type' => 'dictionary', 'dictionary' => ['type' => 'countries']], + ], + 'country' => [ + 'value' => 'USA', + 'field' => ['type' => 'dictionary', 'dictionary' => ['type' => 'countries']], + ], + 'countries' => [ + 'value' => ['AUS', 'USA'], + 'field' => ['type' => 'dictionary', 'dictionary' => ['type' => 'countries'], 'multiple' => true], + ], + 'timezone' => [ + 'value' => 'America/New_York', + 'field' => ['type' => 'dictionary', 'dictionary' => ['type' => 'timezones']], + ], + 'timezones' => [ + 'value' => ['Australia/Sydney', 'America/New_York'], + 'field' => ['type' => 'dictionary', 'dictionary' => ['type' => 'timezones'], 'multiple' => true], + ], + 'currency' => [ + 'value' => 'USD', + 'field' => ['type' => 'dictionary', 'dictionary' => ['type' => 'currencies']], + ], + 'currencies' => [ + 'value' => ['GBP', 'USD'], + 'field' => ['type' => 'dictionary', 'dictionary' => ['type' => 'currencies'], 'multiple' => true], + ], + ]); + + $this->assertGqlEntryHas(' + undefined { name, iso2 } + country { name, iso2 } + countries { name, iso2 } + timezone { name, offset } + timezones { name, offset } + currency { name, code, symbol } + currencies { name, code, symbol } + ', [ + 'undefined' => null, + 'country' => ['name' => 'United States', 'iso2' => 'US'], + 'countries' => [['name' => 'Australia', 'iso2' => 'AU'], ['name' => 'United States', 'iso2' => 'US']], + 'timezone' => ['name' => 'America/New_York', 'offset' => '-04:00'], + 'timezones' => [['name' => 'Australia/Sydney', 'offset' => '+10:00'], ['name' => 'America/New_York', 'offset' => '-04:00']], + 'currency' => ['name' => 'US Dollar', 'code' => 'USD', 'symbol' => '$'], + 'currencies' => [['name' => 'British Pound Sterling', 'code' => 'GBP', 'symbol' => '£'], ['name' => 'US Dollar', 'code' => 'USD', 'symbol' => '$']], + ]); + } +} From 644bb8db0cce830f2ec7b3fabeabab8cf22aeeaf Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Tue, 23 Jul 2024 17:10:48 -0400 Subject: [PATCH 35/76] simplify! --- src/Dictionaries/Countries.php | 6 ---- src/Dictionaries/Currencies.php | 11 ++++++-- src/Dictionaries/Dictionary.php | 21 +++++++++++++- src/Dictionaries/Timezones.php | 6 ---- src/GraphQL/Types/CountryDictionaryType.php | 26 ------------------ src/GraphQL/Types/CurrencyDictionaryType.php | 29 -------------------- src/GraphQL/Types/DictionaryType.php | 19 +++++++++++++ src/GraphQL/Types/TimezoneDictionaryType.php | 26 ------------------ 8 files changed, 47 insertions(+), 97 deletions(-) delete mode 100644 src/GraphQL/Types/CountryDictionaryType.php delete mode 100644 src/GraphQL/Types/CurrencyDictionaryType.php create mode 100644 src/GraphQL/Types/DictionaryType.php delete mode 100644 src/GraphQL/Types/TimezoneDictionaryType.php diff --git a/src/Dictionaries/Countries.php b/src/Dictionaries/Countries.php index 00397d783f..980934cc52 100644 --- a/src/Dictionaries/Countries.php +++ b/src/Dictionaries/Countries.php @@ -4,7 +4,6 @@ use Illuminate\Support\Collection; use Statamic\Facades\File; -use Statamic\GraphQL\Types\CountryDictionaryType; class Countries extends Dictionary { @@ -46,9 +45,4 @@ private function getCountries(): Collection { return collect(json_decode(File::get(__DIR__.'/../../resources/dictionaries/countries.json'), true)); } - - public function getGqlType() - { - return new CountryDictionaryType; - } } diff --git a/src/Dictionaries/Currencies.php b/src/Dictionaries/Currencies.php index fe6af739cc..15c9f9e400 100644 --- a/src/Dictionaries/Currencies.php +++ b/src/Dictionaries/Currencies.php @@ -4,7 +4,7 @@ use Illuminate\Support\Collection; use Statamic\Facades\File; -use Statamic\GraphQL\Types\CurrencyDictionaryType; +use Statamic\Facades\GraphQL; class Currencies extends Dictionary { @@ -33,8 +33,13 @@ private function getCurrencies(): Collection return collect(json_decode(File::get(__DIR__.'/../../resources/dictionaries/currencies.json'), true)); } - public function getGqlType() + protected function getGqlFields(): array { - return new CurrencyDictionaryType; + return [ + ...parent::getGqlFields(), + 'decimal_digits' => [ + 'type' => GraphQL::nonNull(GraphQL::int()), + ], + ]; } } diff --git a/src/Dictionaries/Dictionary.php b/src/Dictionaries/Dictionary.php index a3961e2c7e..5689c4a441 100644 --- a/src/Dictionaries/Dictionary.php +++ b/src/Dictionaries/Dictionary.php @@ -6,6 +6,8 @@ use Statamic\Extend\HasHandle; use Statamic\Extend\HasTitle; use Statamic\Extend\RegistersItself; +use Statamic\Facades\GraphQL; +use Statamic\GraphQL\Types\DictionaryType; abstract class Dictionary { @@ -38,5 +40,22 @@ protected function fieldItems() return $this->fields; } - abstract public function getGqlType(); + public function getGqlType() + { + $name = str(class_basename($this))->singular()->value(); + + return new DictionaryType($name, $this->getGqlFields()); + } + + protected function getGqlFields(): array + { + // By default, we will make non-nullable strings out of all the keys + // of the first option. This is an easy way for it to "just work", + // and of course it may be easily overridden per dictionary. + $firstOption = collect($this->options())->keys()->first(); + + return collect($this->get($firstOption)) + ->map(fn () => ['type' => GraphQL::nonNull(GraphQL::string())]) + ->all(); + } } diff --git a/src/Dictionaries/Timezones.php b/src/Dictionaries/Timezones.php index 4b87ba0944..1817aef334 100644 --- a/src/Dictionaries/Timezones.php +++ b/src/Dictionaries/Timezones.php @@ -4,7 +4,6 @@ use DateTimeZone; use Illuminate\Support\Carbon; -use Statamic\GraphQL\Types\TimezoneDictionaryType; class Timezones extends Dictionary { @@ -33,9 +32,4 @@ private function getOffset(string $tz): string return stripos($offsetInSecs, '-') === false ? "+{$hoursAndSec}" : "-{$hoursAndSec}"; } - - public function getGqlType() - { - return new TimezoneDictionaryType; - } } diff --git a/src/GraphQL/Types/CountryDictionaryType.php b/src/GraphQL/Types/CountryDictionaryType.php deleted file mode 100644 index 0d229cffb4..0000000000 --- a/src/GraphQL/Types/CountryDictionaryType.php +++ /dev/null @@ -1,26 +0,0 @@ - self::NAME, - ]; - - public function fields(): array - { - return [ - 'name' => [ - 'type' => GraphQL::nonNull(GraphQL::string()), - ], - 'iso2' => [ - 'type' => GraphQL::nonNull(GraphQL::string()), - ], - ]; - } -} diff --git a/src/GraphQL/Types/CurrencyDictionaryType.php b/src/GraphQL/Types/CurrencyDictionaryType.php deleted file mode 100644 index 47ff78b5ec..0000000000 --- a/src/GraphQL/Types/CurrencyDictionaryType.php +++ /dev/null @@ -1,29 +0,0 @@ - self::NAME, - ]; - - public function fields(): array - { - return [ - 'name' => [ - 'type' => GraphQL::nonNull(GraphQL::string()), - ], - 'symbol' => [ - 'type' => GraphQL::nonNull(GraphQL::string()), - ], - 'code' => [ - 'type' => GraphQL::nonNull(GraphQL::string()), - ], - ]; - } -} diff --git a/src/GraphQL/Types/DictionaryType.php b/src/GraphQL/Types/DictionaryType.php new file mode 100644 index 0000000000..97524e08ef --- /dev/null +++ b/src/GraphQL/Types/DictionaryType.php @@ -0,0 +1,19 @@ +attributes['name'] = 'Dictionary_'.$name; + $this->fields = $fields; + } + + public function fields(): array + { + return $this->fields; + } +} diff --git a/src/GraphQL/Types/TimezoneDictionaryType.php b/src/GraphQL/Types/TimezoneDictionaryType.php deleted file mode 100644 index 4c182815dd..0000000000 --- a/src/GraphQL/Types/TimezoneDictionaryType.php +++ /dev/null @@ -1,26 +0,0 @@ - self::NAME, - ]; - - public function fields(): array - { - return [ - 'name' => [ - 'type' => GraphQL::nonNull(GraphQL::string()), - ], - 'offset' => [ - 'type' => GraphQL::nonNull(GraphQL::string()), - ], - ]; - } -} From 7d972b9a433e85b34011f1f74dd1cc065750955a Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Wed, 24 Jul 2024 13:39:14 -0400 Subject: [PATCH 36/76] scrap dedicated `multiple` option in favor of max_items 1 like relationship fields --- .../fieldtypes/DictionaryFieldtype.vue | 18 +++++++++++------- src/Fieldtypes/Dictionary.php | 8 +------- .../Fieldtypes/DictionaryFieldtypeTest.php | 12 ++++++------ tests/Fieldtypes/DictionariesTest.php | 4 ++-- 4 files changed, 20 insertions(+), 22 deletions(-) diff --git a/resources/js/components/fieldtypes/DictionaryFieldtype.vue b/resources/js/components/fieldtypes/DictionaryFieldtype.vue index e991b0fe34..b2b76d0a6c 100644 --- a/resources/js/components/fieldtypes/DictionaryFieldtype.vue +++ b/resources/js/components/fieldtypes/DictionaryFieldtype.vue @@ -8,13 +8,13 @@ :calculate-position="positionOptions" :name="name" :clearable="config.clearable" - :disabled="config.disabled || isReadOnly || (config.multiple && limitReached)" + :disabled="config.disabled || isReadOnly || (multiple && limitReached)" :options="normalizeInputOptions(options)" :placeholder="__(config.placeholder)" :searchable="true" :taggable="config.taggable" :push-tags="config.push_tags" - :multiple="config.multiple" + :multiple="multiple" :reset-on-options-change="resetOnOptionsChange" :close-on-select="true" :value="selectedOptions" @@ -23,8 +23,8 @@ @search="search" @search:focus="$emit('focus')" @search:blur="$emit('blur')"> - -