diff --git a/client/client.ts b/client/client.ts index ae80a45a..019d5558 100644 --- a/client/client.ts +++ b/client/client.ts @@ -88,21 +88,6 @@ export class Client { return await makeRequest<{ path: string }>('/folder/move', props) } - // Package - - async packageFetch(props: { - url: string - path?: string - folder?: string - deduplicate?: boolean - }) { - return await makeRequest<{ path: string }>('/package/fetch', props) - } - - async packagePublish(props: { path: string; control: types.IControl }) { - return await makeRequest<{ url: string }>('/package/publish', props) - } - // Table async tableCount(props: { path: string; valid?: boolean }) { diff --git a/client/components/Editors/Package/Layout.tsx b/client/components/Editors/Package/Layout.tsx deleted file mode 100644 index 438985f7..00000000 --- a/client/components/Editors/Package/Layout.tsx +++ /dev/null @@ -1,175 +0,0 @@ -import * as React from 'react' -import capitalize from 'lodash/capitalize' -import Box from '@mui/material/Box' -import Grid from '@mui/material/Grid' -import MenuTree from '../../Parts/Trees/Menu' -import EditorHelp from '../Base/Help' -import SelectField from '../../Parts/Fields/Select' -import Resource from '../Resource' -import Dialect from '../Dialect' -import Schema from '../Schema' -import PackageSection from './Sections/Package' -import LicensesSection from './Sections/Licenses' -import ResourcesSection from './Sections/Resources' -import SourcesSection from './Sections/Sources' -import ContributorsSection from './Sections/Contributors' -import { useStore, selectors, select } from './store' -import * as types from '../../../types' - -export default function Layout() { - return -} - -// TODO: improve menu implementation (move some state to store / reduce re-renders) -function LayoutWithMenu() { - const shallow = useStore((state) => state.shallow) - const section = useStore((state) => state.section) - const resource = useStore(selectors.resource) - const format = useStore(select(selectors.resource, (resource) => resource.format)) - const dialect = useStore(select(selectors.resource, (resource) => resource.dialect)) - const schema = useStore(select(selectors.resource, (resource) => resource.schema)) - const updateHelp = useStore((state) => state.updateHelp) - const updateState = useStore((state) => state.updateState) - const updateResource = useStore((state) => state.updateResource) - const helpItem = useStore((state) => state.helpItem) - - const MENU_ITEMS: types.IMenuItem[] = [ - { section: 'package', name: 'Package' }, - { section: 'package/resources', name: 'Resources' }, - { section: 'package/licenses', name: 'Licenses' }, - { section: 'package/contributors', name: 'Contributors' }, - { section: 'package/sources', name: 'Sources' }, - ] - - if (!shallow) { - MENU_ITEMS.push( - ...[ - { section: 'resource', name: 'Resource' }, - { section: 'resource/integrity', name: 'Integrity' }, - { section: 'resource/licenses', name: 'Licenses' }, - { section: 'resource/contributors', name: 'Contributors' }, - { section: 'resource/sources', name: 'Sources' }, - { section: 'dialect', name: 'Dialect', disabled: resource.type !== 'table' }, - { section: 'dialect/format', name: capitalize(format) || 'Format' }, - { section: 'schema', name: 'Schema', disabled: resource.type !== 'table' }, - { section: 'schema/fields', name: 'Fields' }, - { section: 'schema/foreignKeys', name: 'Foreign Keys' }, - ] - ) - } - - // We use memo to avoid nested editors re-rerender - const handleResourceChange = React.useMemo(() => { - return (resource: types.IResource) => updateResource(resource) - }, []) - const handleDialectChange = React.useMemo(() => { - return (dialect: types.IDialect) => updateResource({ dialect }) - }, []) - const handleSchemaChange = React.useMemo(() => { - return (schema: types.ISchema) => updateResource({ schema }) - }, []) - - // We use memo to avoid nested editors re-rerender - const externalMenu = React.useMemo(() => { - return { section } - }, []) - React.useEffect(() => { - const isTabular = section.startsWith('dialect') || section.startsWith('schema') - if (resource.type !== 'table' && isTabular) { - updateHelp('resource') - updateState({ section: 'resource' }) - externalMenu.section = 'resource' - } - }, [resource]) - - return ( - - - {!shallow && ( - - - - )} - { - updateHelp(section) - updateState({ section }) - externalMenu.section = section - }} - /> - - - - {!shallow && ( - - - {resource.type === 'table' && ( - - - - - )} - - )} - - - - - - ) -} - -function Sections() { - const section = useStore((state) => state.section) - if (!section) return null - if (section == 'package') return - if (section == 'package/resources') return - if (section == 'package/licenses') return - if (section == 'package/contributors') return - if (section == 'package/sources') return - return null -} - -export function ResourceSelector() { - const resource = useStore(selectors.resource) - const updateResourceState = useStore((state) => state.updateResourceState) - const resourceNames = useStore(selectors.resourceNames) - if (!resource) return null - return ( - updateResourceState({ index: resourceNames.indexOf(value) })} - /> - ) -} diff --git a/client/components/Editors/Package/Sections/Contributors.tsx b/client/components/Editors/Package/Sections/Contributors.tsx deleted file mode 100644 index ccebec2c..00000000 --- a/client/components/Editors/Package/Sections/Contributors.tsx +++ /dev/null @@ -1,146 +0,0 @@ -import * as React from 'react' -import Box from '@mui/material/Box' -import Columns from '../../../Parts/Grids/Columns' -import InputField from '../../../Parts/Fields/Input' -import EditorItem from '../../Base/Item' -import EditorList from '../../Base/List' -import EditorListItem from '../../Base/ListItem' -import EditorSearch from '../../Base/Search' -import { useStore, selectors, select } from '../store' -import validator from 'validator' - -export default function Contributors() { - const index = useStore((state) => state.contributorState.index) - return index === undefined ? : -} - -function ContributorList() { - const query = useStore((state) => state.contributorState.query) - const contributorItems = useStore(selectors.contributorItems) - const updateContributorState = useStore((state) => state.updateContributorState) - const addContributor = useStore((state) => state.addContributor) - const removeContributor = useStore((state) => state.removeContributor) - return ( - addContributor()} - SearchInput={ - updateContributorState({ query })} - /> - } - > - {contributorItems.map(({ index, contributor }) => ( - { - updateContributorState({ index }) - }} - onRemoveClick={() => removeContributor(index)} - /> - ))} - - ) -} - -function ContributorItem() { - const title = useStore( - select(selectors.contributor, (contributor) => contributor.title) - ) - const isExtras = useStore((state) => state.contributorState.isExtras) - const updateContributorState = useStore((state) => state.updateContributorState) - return ( - updateContributorState({ isExtras: !isExtras })} - onBackClick={() => updateContributorState({ index: undefined, isExtras: false })} - > - - - - <Email /> - </Box> - <Box> - <Path /> - <Role /> - </Box> - </Columns> - </EditorItem> - ) -} - -function Title() { - const title = useStore( - select(selectors.contributor, (contributor) => contributor.title) - ) - const updateHelp = useStore((state) => state.updateHelp) - const updateContributor = useStore((state) => state.updateContributor) - return ( - <InputField - label="Title" - value={title || ''} - onFocus={() => updateHelp('package/contributors/title')} - onChange={(title) => updateContributor({ title })} - /> - ) -} - -function Email() { - const email = useStore( - select(selectors.contributor, (contributor) => contributor.email) - ) - const updateHelp = useStore((state) => state.updateHelp) - const updateContributor = useStore((state) => state.updateContributor) - const [isValid, setIsValid] = React.useState(isValidEmail()) - function isValidEmail() { - return email ? validator.isEmail(email) : true - } - return ( - <InputField - error={!isValid} - label="Email" - value={email || ''} - onFocus={() => updateHelp('package/contributors/email')} - onBlur={() => { - setIsValid(isValidEmail()) - }} - onChange={(value) => updateContributor({ email: value })} - helperText={!isValid ? 'Email is not valid.' : ''} - /> - ) -} - -function Path() { - const path = useStore(select(selectors.contributor, (contributor) => contributor.path)) - const updateHelp = useStore((state) => state.updateHelp) - const updateContributor = useStore((state) => state.updateContributor) - return ( - <InputField - label="Path" - value={path || ''} - onFocus={() => updateHelp('package/contributors/path')} - onChange={(path) => updateContributor({ path })} - /> - ) -} - -function Role() { - const role = useStore(select(selectors.contributor, (contributor) => contributor.role)) - const updateHelp = useStore((state) => state.updateHelp) - const updateContributor = useStore((state) => state.updateContributor) - return ( - <InputField - label="Role" - value={role || ''} - onFocus={() => updateHelp('package/contributors/role')} - onChange={(role) => updateContributor({ role })} - /> - ) -} diff --git a/client/components/Editors/Package/Sections/Licenses.tsx b/client/components/Editors/Package/Sections/Licenses.tsx deleted file mode 100644 index 0b42323c..00000000 --- a/client/components/Editors/Package/Sections/Licenses.tsx +++ /dev/null @@ -1,123 +0,0 @@ -import * as React from 'react' -import Box from '@mui/material/Box' -import Columns from '../../../Parts/Grids/Columns' -import InputField from '../../../Parts/Fields/Input' -import EditorItem from '../../Base/Item' -import EditorList from '../../Base/List' -import EditorListItem from '../../Base/ListItem' -import EditorSearch from '../../Base/Search' -import { useStore, selectors, select } from '../store' -import validator from 'validator' - -export default function Licenses() { - const index = useStore((state) => state.licenseState.index) - return index === undefined ? <LicenseList /> : <LicenseItem /> -} - -function LicenseList() { - const query = useStore((state) => state.licenseState.query) - const licenseItems = useStore(selectors.licenseItems) - const updateLicenseState = useStore((state) => state.updateLicenseState) - const addLicense = useStore((state) => state.addLicense) - const removeLicense = useStore((state) => state.removeLicense) - return ( - <EditorList - kind="license" - query={query} - onAddClick={() => addLicense()} - SearchInput={ - <EditorSearch - value={query || ''} - onChange={(query) => updateLicenseState({ query })} - /> - } - > - {licenseItems.map(({ index, license }) => ( - <EditorListItem - key={index} - kind="license" - name={license.name} - type="license" - onClick={() => updateLicenseState({ index })} - onRemoveClick={() => removeLicense(index)} - /> - ))} - </EditorList> - ) -} - -function LicenseItem() { - const name = useStore(select(selectors.license, (license) => license.name)) - const isExtras = useStore((state) => state.licenseState.isExtras) - const updateLicenseState = useStore((state) => state.updateLicenseState) - return ( - <EditorItem - kind="license" - name={name} - isExtras={isExtras} - onExtrasClick={() => updateLicenseState({ isExtras: !isExtras })} - onBackClick={() => updateLicenseState({ index: undefined, isExtras: false })} - > - <Columns spacing={3}> - <Box> - <Name /> - <Title /> - </Box> - <Box> - <Path /> - </Box> - </Columns> - </EditorItem> - ) -} - -function Name() { - const name = useStore(select(selectors.license, (license) => license.name)) - const updateHelp = useStore((state) => state.updateHelp) - const updateLicense = useStore((state) => state.updateLicense) - return ( - <InputField - label="Name" - value={name} - onFocus={() => updateHelp('package/licenses/name')} - onChange={(name) => updateLicense({ name })} - /> - ) -} - -function Title() { - const title = useStore(select(selectors.license, (license) => license.title)) - const updateHelp = useStore((state) => state.updateHelp) - const updateLicense = useStore((state) => state.updateLicense) - const [isValid, setIsValid] = React.useState(isValidTitle()) - function isValidTitle() { - return title ? !validator.isNumeric(title) : true - } - return ( - <InputField - error={!isValid} - label="Title" - value={title || ''} - onFocus={() => updateHelp('package/licenses/title')} - onBlur={() => { - setIsValid(isValidTitle()) - }} - onChange={(value) => updateLicense({ title: value || undefined })} - helperText={!isValid ? 'Title is not valid.' : ''} - /> - ) -} - -function Path() { - const path = useStore(select(selectors.license, (license) => license.path)) - const updateHelp = useStore((state) => state.updateHelp) - const updateLicense = useStore((state) => state.updateLicense) - return ( - <InputField - label="Path" - value={path || ''} - onFocus={() => updateHelp('package/licenses/path')} - onChange={(path) => updateLicense({ path })} - /> - ) -} diff --git a/client/components/Editors/Package/Sections/Package.tsx b/client/components/Editors/Package/Sections/Package.tsx deleted file mode 100644 index 0c8e8b6c..00000000 --- a/client/components/Editors/Package/Sections/Package.tsx +++ /dev/null @@ -1,165 +0,0 @@ -import * as React from 'react' -import Grid from '@mui/material/Grid' -import InputField from '../../../Parts/Fields/Input' -import DateTimePickerField from '../../../Parts/Fields/DateTimePicker' -import MultilineField from '../../../Parts/Fields/Multiline' -import EditorSection from '../../Base/Section' -import { useStore } from '../store' -import validator from 'validator' -import dayjs from 'dayjs' - -export default function Package() { - const updateHelp = useStore((state) => state.updateHelp) - return ( - <EditorSection name="Package" onHeadingClick={() => updateHelp('package')}> - <Grid container spacing={1} justifyContent="center"> - <Grid item xs={6}> - <Name /> - <Title /> - <Description /> - <Keywords /> - </Grid> - <Grid item xs={6}> - <Homepage /> - <Version /> - <Created /> - <Image /> - </Grid> - </Grid> - </EditorSection> - ) -} - -function Name() { - const name = useStore((state) => state.descriptor.name) - const updateHelp = useStore((state) => state.updateHelp) - const updateDescriptor = useStore((state) => state.updateDescriptor) - const [isValid, setIsValid] = React.useState(isValidName()) - function isValidName() { - return name ? validator.isSlug(name) : true - } - return ( - <InputField - error={!isValid} - label="Name" - value={name || ''} - onFocus={() => updateHelp('package/name')} - onBlur={() => { - setIsValid(isValidName()) - }} - onChange={(value) => updateDescriptor({ name: value || undefined })} - helperText={!isValid ? 'Name is not valid.' : ''} - /> - ) -} - -function Title() { - const title = useStore((state) => state.descriptor.title) - const updateHelp = useStore((state) => state.updateHelp) - const updateDescriptor = useStore((state) => state.updateDescriptor) - const [isValid, setIsValid] = React.useState(isValidTitle()) - function isValidTitle() { - return title ? !validator.isNumeric(title) : true - } - return ( - <InputField - label="Title" - error={!isValid} - value={title || ''} - onFocus={() => updateHelp('package/title')} - onBlur={() => { - setIsValid(isValidTitle()) - }} - onChange={(value) => updateDescriptor({ title: value || undefined })} - helperText={!isValid ? 'Title is not valid.' : ''} - /> - ) -} - -function Description() { - const description = useStore((state) => state.descriptor.description) - const updateHelp = useStore((state) => state.updateHelp) - const updateDescriptor = useStore((state) => state.updateDescriptor) - return ( - <MultilineField - label="Description" - value={description || ''} - onFocus={() => updateHelp('package/description')} - onChange={(value) => updateDescriptor({ description: value || undefined })} - /> - ) -} - -function Homepage() { - const homepage = useStore((state) => state.descriptor.homepage) - const updateHelp = useStore((state) => state.updateHelp) - const updateDescriptor = useStore((state) => state.updateDescriptor) - return ( - <InputField - label="Homepage" - value={homepage} - onFocus={() => updateHelp('package/homepage')} - onChange={(value) => updateDescriptor({ homepage: value || undefined })} - /> - ) -} - -function Version() { - const version = useStore((state) => state.descriptor.version) - const updateHelp = useStore((state) => state.updateHelp) - const updateDescriptor = useStore((state) => state.updateDescriptor) - return ( - <InputField - label="Version" - value={version} - onFocus={() => updateHelp('package/version')} - onChange={(value) => updateDescriptor({ version: value || undefined })} - /> - ) -} -function Created() { - const created = useStore((state) => state.descriptor.created) - const updateDescriptor = useStore((state) => state.updateDescriptor) - const updateHelp = useStore((state) => state.updateHelp) - return ( - <DateTimePickerField - label="Created" - value={created ? dayjs(created) : null} - onFocus={() => updateHelp('package/created')} - onChange={(value) => { - updateDescriptor({ created: value?.format('YYYY-MM-DDTHH:mm:ss') }) - }} - errorMessage={'Date is not valid'} - /> - ) -} - -function Keywords() { - const keywords = useStore((state) => state.descriptor.keywords) - const updateHelp = useStore((state) => state.updateHelp) - const updateDescriptor = useStore((state) => state.updateDescriptor) - return ( - <InputField - label="Keywords" - value={keywords} - onFocus={() => updateHelp('package/keywords')} - onChange={(value) => - updateDescriptor({ keywords: value ? value.split(',') : undefined }) - } - /> - ) -} - -function Image() { - const image = useStore((state) => state.descriptor.image) - const updateHelp = useStore((state) => state.updateHelp) - const updateDescriptor = useStore((state) => state.updateDescriptor) - return ( - <InputField - label="Image" - value={image} - onFocus={() => updateHelp('package/image')} - onChange={(value) => updateDescriptor({ image: value || undefined })} - /> - ) -} diff --git a/client/components/Editors/Package/Sections/Resources.tsx b/client/components/Editors/Package/Sections/Resources.tsx deleted file mode 100644 index b4c27aae..00000000 --- a/client/components/Editors/Package/Sections/Resources.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import EditorList from '../../Base/List' -import EditorListItem from '../../Base/ListItem' -import EditorSearch from '../../Base/Search' -import { useStore, selectors } from '../store' - -export default function Resources() { - const query = useStore((state) => state.resourceState.query) - const resourceItems = useStore(selectors.resourceItems) - const updateResourceState = useStore((state) => state.updateResourceState) - const addResource = useStore((state) => state.addResource) - const removeResource = useStore((state) => state.removeResource) - const onResourceSelected = useStore((state) => state.onResourceSelected) - return ( - <EditorList - kind="resource" - query={query} - onAddClick={() => addResource()} - SearchInput={ - <EditorSearch - value={query || ''} - onChange={(query) => updateResourceState({ query })} - /> - } - > - {resourceItems.map(({ index, resource }) => ( - <EditorListItem - title="Select Resource" - key={index} - kind="resource" - name={resource.name} - type={resource.type} - onClick={() => { - updateResourceState({ index }) - if (onResourceSelected) onResourceSelected(resource.name) - }} - onRemoveClick={() => removeResource(index)} - /> - ))} - </EditorList> - ) -} diff --git a/client/components/Editors/Package/Sections/Sources.tsx b/client/components/Editors/Package/Sections/Sources.tsx deleted file mode 100644 index 7aaa2d2c..00000000 --- a/client/components/Editors/Package/Sections/Sources.tsx +++ /dev/null @@ -1,125 +0,0 @@ -import * as React from 'react' -import Box from '@mui/material/Box' -import Columns from '../../../Parts/Grids/Columns' -import InputField from '../../../Parts/Fields/Input' -import EditorItem from '../../Base/Item' -import EditorList from '../../Base/List' -import EditorListItem from '../../Base/ListItem' -import EditorSearch from '../../Base/Search' -import { useStore, selectors, select } from '../store' -import validator from 'validator' - -export default function Sources() { - const index = useStore((state) => state.sourceState.index) - return index === undefined ? <SourceList /> : <SourceItem /> -} - -function SourceList() { - const query = useStore((state) => state.sourceState.query) - const sourceItems = useStore(selectors.sourceItems) - const updateSourceState = useStore((state) => state.updateSourceState) - const addSource = useStore((state) => state.addSource) - const removeSource = useStore((state) => state.removeSource) - return ( - <EditorList - kind="source" - query={query} - onAddClick={() => addSource()} - SearchInput={ - <EditorSearch - value={query || ''} - onChange={(query) => updateSourceState({ query })} - /> - } - > - {sourceItems.map(({ index, source }) => ( - <EditorListItem - key={index} - kind="source" - name={source.title} - type="source" - onClick={() => { - updateSourceState({ index }) - }} - onRemoveClick={() => removeSource(index)} - /> - ))} - </EditorList> - ) -} - -function SourceItem() { - const title = useStore(select(selectors.source, (source) => source.title)) - const isExtras = useStore((state) => state.sourceState.isExtras) - const updateSourceState = useStore((state) => state.updateSourceState) - return ( - <EditorItem - kind="source" - name={title} - isExtras={isExtras} - onExtrasClick={() => updateSourceState({ isExtras: !isExtras })} - onBackClick={() => updateSourceState({ index: undefined, isExtras: false })} - > - <Columns spacing={3}> - <Box> - <Title /> - <Email /> - </Box> - <Box> - <Path /> - </Box> - </Columns> - </EditorItem> - ) -} - -function Title() { - const title = useStore(select(selectors.source, (source) => source.title)) - const updateHelp = useStore((state) => state.updateHelp) - const updateSource = useStore((state) => state.updateSource) - return ( - <InputField - label="Title" - value={title} - onFocus={() => updateHelp('package/sources/title')} - onChange={(title) => updateSource({ title })} - /> - ) -} - -function Path() { - const path = useStore(select(selectors.source, (source) => source.path)) - const updateHelp = useStore((state) => state.updateHelp) - const updateSource = useStore((state) => state.updateSource) - return ( - <InputField - label="Path" - value={path || ''} - onFocus={() => updateHelp('package/sources/path')} - onChange={(path) => updateSource({ path })} - /> - ) -} - -function Email() { - const email = useStore(select(selectors.source, (source) => source.email)) - const updateHelp = useStore((state) => state.updateHelp) - const updateSource = useStore((state) => state.updateSource) - const [isValid, setIsValid] = React.useState(isValidEmail()) - function isValidEmail() { - return email ? validator.isEmail(email) : true - } - return ( - <InputField - error={!isValid} - label="Email" - value={email || ''} - onFocus={() => updateHelp('package/sources/email')} - onBlur={() => { - setIsValid(isValidEmail()) - }} - onChange={(value) => updateSource({ email: value || undefined })} - helperText={!isValid ? 'Email is not valid.' : ''} - /> - ) -} diff --git a/client/components/Editors/Package/help.yaml b/client/components/Editors/Package/help.yaml deleted file mode 100644 index f4815931..00000000 --- a/client/components/Editors/Package/help.yaml +++ /dev/null @@ -1,124 +0,0 @@ -# Package - -package: - - Package - - https://specs.frictionlessdata.io/data-package/ - - A simple container format for describing a coherent collection of data in a single package. It provides the basis for convenient delivery, installation and management of datasets. - -package/name: - - Name - - https://specs.frictionlessdata.io/data-package/#name - - Slugified the name of the package. - -package/title: - - Title - - https://specs.frictionlessdata.io/data-package/#title - - A human-readable title. - -package/description: - - Description - - https://specs.frictionlessdata.io/data-package/#description - - A description of the package. The description MUST be markdown formatted – this also allows for simple plain text as plain text is itself valid markdown. - -package/homepage: - - Homepage - - https://specs.frictionlessdata.io/data-package/#homepage - - A URL for the home on the web that is related to this data package. - -package/keywords: - - Keywords - - https://specs.frictionlessdata.io/data-package/#keywords - - An Array of string keywords to assist users searching for the package in catalogs. - -package/version: - - Version - - https://specs.frictionlessdata.io/data-package/#version - - A version string identifying the version of the package. - -package/created: - - Created - - https://specs.frictionlessdata.io/data-package/#created - - The datetime on which this was created. - -package/image: - - Image - - https://specs.frictionlessdata.io/data-package/#image - - An image path to use for this data package. - -# Resources - -package/resources: - - Resources - - https://specs.frictionlessdata.io/data-package/#resource-information - - Packaged data resources are described in the resources property of the package descriptor. - -# Licenses - -package/licenses: - - Licenses - - https://specs.frictionlessdata.io/data-package/#licenses - - The license(s) under which the resource is provided. - -package/licenses/name: - - Name - - https://specs.frictionlessdata.io/data-package/#licenses - - The name MUST be an Open Definition license ID e.g. ODC-BY-1.0 - -package/licenses/path: - - Path - - https://specs.frictionlessdata.io/data-package/#licenses - - A url-or-path string, that is a fully qualified HTTP address, or a relative POSIX path for this license. - -package/licenses/title: - - Title - - https://specs.frictionlessdata.io/data-package/#licenses - - A human-readable title or label for this license e.g. "Open Data Commons Public Domain Dedication and License v1.0". - -# Contributors - -package/contributors: - - Contributors - - https://specs.frictionlessdata.io/data-package/#contributors - - A name/title of the contributor (name for person, name/title of organization). - -package/contributors/title: - - Title - - https://specs.frictionlessdata.io/data-package/#contributors - - Title of the source (e.g. document or organization name). - -package/contributors/email: - - Email - - https://specs.frictionlessdata.io/data-package/#contributors - - An email address. - -package/contributors/path: - - Path - - https://specs.frictionlessdata.io/data-package/#contributors - - A fully qualified http URL pointing to a relevant location online for the contributor. - -package/contributors/role: - - Role - - https://specs.frictionlessdata.io/data-package/#contributors - - A string describing the role of the contributor. - -# Sources - -package/sources: - - Sources - - https://specs.frictionlessdata.io/data-package/#sources - - Raw sources for the data package. - -package/sources/title: - - Title - - https://specs.frictionlessdata.io/data-package/#sources - - Title of the source (e.g. document or organization name). - -package/sources/path: - - Path - - https://specs.frictionlessdata.io/data-package/#sources - - A url-or-path string, that is a fully qualified HTTP address, or a relative POSIX path. - -package/sources/email: - - Email - - https://specs.frictionlessdata.io/data-package/#sources - - An email address. diff --git a/client/components/Editors/Package/index.tsx b/client/components/Editors/Package/index.tsx deleted file mode 100644 index 392af6b8..00000000 --- a/client/components/Editors/Package/index.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import * as React from 'react' -import { StoreProvider, makeStore } from './store' -import * as types from '../../../types' -import Layout from './Layout' - -export interface PackageProps { - package?: types.IPackage - shallow?: boolean - onChange?: (pkg: types.IPackage) => void - onAddResource?: () => void - onResourceSelected?: (name?: string) => void -} - -export default function Package(props: PackageProps) { - const store = React.useMemo(() => makeStore(props), Object.values(props)) - return ( - <StoreProvider value={store}> - <Layout /> - </StoreProvider> - ) -} diff --git a/client/components/Editors/Package/store.ts b/client/components/Editors/Package/store.ts deleted file mode 100644 index bb9253ae..00000000 --- a/client/components/Editors/Package/store.ts +++ /dev/null @@ -1,304 +0,0 @@ -import * as React from 'react' -import * as zustand from 'zustand' -import { assert } from 'ts-essentials' -import noop from 'lodash/noop' -import cloneDeep from 'lodash/cloneDeep' -import { createStore } from 'zustand/vanilla' -import { createSelector } from 'reselect' -import { PackageProps } from './index' -import * as settings from '../../../settings' -import * as helpers from '../../../helpers' -import * as types from '../../../types' -import help from './help.yaml' - -const DEFAULT_HELP_ITEM = helpers.readHelpItem(help, 'package')! - -interface ISectionState { - query?: string - index?: number - isExtras?: boolean -} - -interface State { - descriptor: types.IPackage - shallow?: boolean - onChange: (pkg: types.IPackage) => void - onAddResource?: () => void - onResourceSelected?: (name: string) => void - section: string - helpItem: types.IHelpItem - updateState: (patch: Partial<State>) => void - updateHelp: (path: string) => void - updateDescriptor: (patch: Partial<types.IPackage>) => void - - // Resources - - resourceState: ISectionState & { index: number } - updateResourceState: (patch: Partial<ISectionState>) => void - updateResource: (patch: Partial<types.IResource>) => void - removeResource: (index: number) => void - addResource: () => void - - // Licenses - - licenseState: ISectionState - updateLicenseState: (patch: Partial<ISectionState>) => void - updateLicense: (patch: Partial<types.ILicense>) => void - removeLicense: (index: number) => void - addLicense: () => void - - // Sources - - sourceState: ISectionState - updateSourceState: (patch: Partial<ISectionState>) => void - updateSource: (patch: Partial<types.ISource>) => void - removeSource: (index: number) => void - addSource: () => void - - // Contributors - - contributorState: ISectionState - updateContributorState: (patch: Partial<ISectionState>) => void - updateContributor: (patch: Partial<types.IContributor>) => void - removeContributor: (index: number) => void - addContributor: () => void -} - -export function makeStore(props: PackageProps) { - return createStore<State>((set, get) => ({ - descriptor: props.package || cloneDeep(settings.INITIAL_PACKAGE), - shallow: props.shallow, - onChange: props.onChange || noop, - onAddResource: props.onAddResource, - onResourceSelected: props.onResourceSelected, - section: 'package', - helpItem: DEFAULT_HELP_ITEM, - updateState: (patch) => { - set({ ...patch }) - }, - updateHelp: (path) => { - const helpItem = helpers.readHelpItem(help, path) || DEFAULT_HELP_ITEM - set({ helpItem }) - }, - updateDescriptor: (patch) => { - const { descriptor, onChange } = get() - Object.assign(descriptor, patch) - onChange(descriptor) - set({ descriptor }) - }, - - // Resources - - resourceState: { index: 0 }, - updateResourceState: (patch) => { - const { resourceState } = get() - set({ resourceState: { ...resourceState, ...patch } }) - }, - updateResource: (patch) => { - const { descriptor, updateDescriptor } = get() - const resource = selectors.resource(get())! - const resources = descriptor.resources! - Object.assign(resource, patch) - updateDescriptor({ resources }) - }, - removeResource: (index) => { - const { descriptor, updateDescriptor, updateResourceState } = get() - const resources = [...(descriptor.resources || [])] - resources.splice(index, 1) - updateResourceState({ index: undefined, isExtras: false }) - updateDescriptor({ resources }) - }, - // TODO: scroll to newly created resource - addResource: () => { - const { descriptor, updateDescriptor, onAddResource } = get() - if (onAddResource) return onAddResource() - const resources = [...(descriptor.resources || [])] - const name = helpers.generateTitle(resources, 'resource') - resources.push({ - name, - type: 'table', - path: 'table.csv', - }) - updateDescriptor({ resources }) - }, - - // Licenses - - licenseState: {}, - updateLicenseState: (patch) => { - const { licenseState } = get() - set({ licenseState: { ...licenseState, ...patch } }) - }, - updateLicense: (patch) => { - const { descriptor, updateDescriptor, licenseState } = get() - const index = licenseState.index! - const license = selectors.license(get()) - const licenses = descriptor.licenses! - licenses[index] = { ...license, ...patch } - updateDescriptor({ licenses }) - }, - removeLicense: (index) => { - const { descriptor, updateDescriptor, updateLicenseState } = get() - const licenses = [...(descriptor.licenses || [])] - licenses.splice(index, 1) - updateLicenseState({ index: undefined, isExtras: false }) - updateDescriptor({ licenses }) - }, - // TODO: scroll to newly created license - addLicense: () => { - const { descriptor, updateDescriptor } = get() - const licenses = [...(descriptor.licenses || [])] - licenses.push({ name: 'MIT' }) - updateDescriptor({ licenses }) - }, - - // Sources - - sourceState: {}, - updateSourceState: (patch) => { - const { sourceState } = get() - set({ sourceState: { ...sourceState, ...patch } }) - }, - updateSource: (patch) => { - const { descriptor, sourceState, updateDescriptor } = get() - const index = sourceState.index! - const source = selectors.source(get()) - const sources = descriptor.sources! - sources[index] = { ...source, ...patch } - updateDescriptor({ sources }) - }, - removeSource: (index) => { - const { descriptor, updateDescriptor, updateSourceState } = get() - const sources = [...(descriptor.sources || [])] - sources.splice(index, 1) - updateSourceState({ index: undefined, isExtras: false }) - updateDescriptor({ sources }) - }, - // TODO: scroll to newly created source - addSource: () => { - const { descriptor, updateDescriptor } = get() - const sources = [...(descriptor.sources || [])] - sources.push({ title: helpers.generateTitle(sources, 'source') }) - updateDescriptor({ sources }) - }, - - // Contributors - - contributorState: {}, - updateContributorState: (patch) => { - const { contributorState } = get() - set({ contributorState: { ...contributorState, ...patch } }) - }, - updateContributor: (patch) => { - const { descriptor, contributorState, updateDescriptor } = get() - const index = contributorState.index! - const contributor = selectors.contributor(get()) - const contributors = descriptor.contributors! - contributors[index] = { ...contributor, ...patch } - updateDescriptor({ contributors }) - }, - removeContributor: (index) => { - const { descriptor, updateDescriptor, updateContributorState } = get() - const contributors = [...(descriptor.contributors || [])] - contributors.splice(index, 1) - updateContributorState({ index: undefined, isExtras: false }) - updateDescriptor({ contributors }) - }, - // TODO: scroll to newly created contributor - addContributor: () => { - const { descriptor, updateDescriptor } = get() - const contributors = [...(descriptor.contributors || [])] - contributors.push({ title: helpers.generateTitle(contributors, 'contributor') }) - updateDescriptor({ contributors }) - }, - })) -} - -export const select = createSelector -export const selectors = { - // Resources - - resource: (state: State) => { - const index = state.resourceState.index - const resources = state.descriptor.resources || [] - const resource = resources[index] ?? {} - return resource - }, - resourceItems: (state: State) => { - const items = [] - const query = state.resourceState.query - for (const [index, resource] of (state.descriptor.resources || []).entries()) { - if (query && !resource.name.toLowerCase().includes(query.toLowerCase())) continue - items.push({ index, resource }) - } - return items - }, - resourceNames: (state: State) => { - return state.descriptor.resources.map((resource) => resource.name) - }, - - // Licenses - - license: (state: State) => { - const index = state.licenseState.index! - const licenses = state.descriptor.licenses || [] - const license = licenses[index] ?? {} - return license - }, - licenseItems: (state: State) => { - const items = [] - const query = state.licenseState.query - for (const [index, license] of (state.descriptor.licenses || []).entries()) { - if (query && !license.name.toLowerCase().includes(query.toLowerCase())) continue - items.push({ index, license }) - } - return items - }, - - // Sources - - source: (state: State) => { - const index = state.sourceState.index! - const sources = state.descriptor.sources || [] - const source = sources[index] ?? {} - return source - }, - sourceItems: (state: State) => { - const items = [] - const query = state.sourceState.query - for (const [index, source] of (state.descriptor.sources || []).entries()) { - if (query && !source.title.toLowerCase().includes(query.toLowerCase())) continue - items.push({ index, source }) - } - return items - }, - - // Contributors - - contributor: (state: State) => { - const index = state.contributorState.index! - const contributors = state.descriptor.contributors || [] - const contributor = contributors[index] ?? {} - return contributor - }, - contributorItems: (state: State) => { - const items = [] - const query = state.contributorState.query - for (const [index, contributor] of (state.descriptor.contributors || []).entries()) { - if (query && !contributor.title.toLowerCase().includes(query.toLowerCase())) { - continue - } - items.push({ index, contributor }) - } - return items - }, -} - -export function useStore<R>(selector: (state: State) => R): R { - const store = React.useContext(StoreContext) - assert(store, 'store provider is required') - return zustand.useStore(store, selector) -} - -const StoreContext = React.createContext<zustand.StoreApi<State> | null>(null) -export const StoreProvider = StoreContext.Provider diff --git a/client/components/Parts/Trees/File.tsx b/client/components/Parts/Trees/File.tsx index 0667c9c8..647c5c75 100644 --- a/client/components/Parts/Trees/File.tsx +++ b/client/components/Parts/Trees/File.tsx @@ -278,8 +278,6 @@ function TreeItemIcon(props: { nodeId: string; item: types.IFileTreeItem }) { : theme.palette.OKFNGreenBlue.main const fontWeight = 'normal' - // When data package is enabled consider highlighting it - // const fontWeight = props.item.type === 'package' ? 'bold' : 'normal' return ( <Box diff --git a/client/helpers/general.ts b/client/helpers/general.ts index 971febd7..e28e7975 100644 --- a/client/helpers/general.ts +++ b/client/helpers/general.ts @@ -8,8 +8,6 @@ export function exportDescriptor(descriptor: object) { // TODO: cloneDeep here? export function getInitialDescriptor(type?: string) { switch (type) { - case 'package': - return settings.INITIAL_PACKAGE case 'resource': return settings.INITIAL_RESOURCE case 'dialect': diff --git a/client/settings.ts b/client/settings.ts index 51aaf724..d9d35212 100644 --- a/client/settings.ts +++ b/client/settings.ts @@ -17,10 +17,9 @@ import TableViewIcon from '@mui/icons-material/TableView' import GridOnIcon from '@mui/icons-material/GridOn' import * as types from './types' -// Genearl +// General export const SERVER_URL = 'http://localhost:4040' -export const PACKAGE_PATH = 'datapackage.json' export const HASHINGS = ['md5', 'sha256'] export const ENCODINGS = ['utf-8', 'iso-8859-1'] export const MISSING_VALUES = ['""', 'n/a', 'na', 'N/A', 'NA'] @@ -54,7 +53,6 @@ export const FILE_TYPES = [ 'image', 'json', 'map', - 'package', 'resource', 'schema', 'script', @@ -67,7 +65,6 @@ export const TEXT_FILE_TYPES = [ 'chart', 'dialect', 'json', - 'package', 'resource', 'schema', 'script', @@ -113,7 +110,6 @@ export const INITIAL_CONFIG: types.IConfig = { system: {}, project: {}, folder: export const INITIAL_SCHEMA: types.ISchema = { fields: [] } export const INITIAL_DIALECT: types.IDialect = {} export const INITIAL_CONTROL: Partial<types.IControl> = { type: 'ckan' } -export const INITIAL_PACKAGE: types.IPackage = { resources: [] } export const INITIAL_PORTAL: types.IPortal = { type: 'ckan' } export const INITIAL_HISTORY: types.IHistory = { changes: [] } export const INITIAL_RESOURCE: types.IResource = { @@ -134,7 +130,6 @@ export const TYPE_ICONS: { [key: string]: React.ElementType } = { folder: FolderIcon, image: ImageIcon, map: MapIcon, - package: [SourceIcon, WidgetsIcon][0], pipeline: AccountTree, resource: DescriptionIcon, schema: DescriptionIcon, diff --git a/client/types/index.ts b/client/types/index.ts index 253eea42..04cb0d55 100644 --- a/client/types/index.ts +++ b/client/types/index.ts @@ -9,7 +9,6 @@ export * from './help' export * from './history' export * from './license' export * from './menu' -export * from './package' export * from './portal' export * from './progress' export * from './record' diff --git a/client/types/package.ts b/client/types/package.ts deleted file mode 100644 index 7c5a9dba..00000000 --- a/client/types/package.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { IResource } from './resource' -import { IContributor } from './contributor' -import { ILicense } from './license' -import { ISource } from './source' - -export interface IPackage { - name?: string - title?: string - description?: string - licenses?: ILicense[] - homepage?: string - version?: string - created?: string - image?: string - resources: IResource[] - contributors?: IContributor[] - keywords?: string[] - sources?: ISource[] -} diff --git a/server/helpers/resource.py b/server/helpers/resource.py index ae01d22f..a993845c 100644 --- a/server/helpers/resource.py +++ b/server/helpers/resource.py @@ -3,7 +3,7 @@ from typing import TYPE_CHECKING from frictionless import FrictionlessException, Indexer, Report -from frictionless.resources import PackageResource, ResourceResource, TableResource +from frictionless.resources import ResourceResource, TableResource if TYPE_CHECKING: from frictionless import Resource @@ -27,7 +27,7 @@ def index_resource(project: Project, resource: Resource, table_name: str): report = indexer.index() # Container resource - if isinstance(resource, (ResourceResource, PackageResource)): + if isinstance(resource, (ResourceResource)): try: errors = [] resource.read_metadata()