-
Notifications
You must be signed in to change notification settings - Fork 13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: EntityEditor class #1127
Merged
Merged
feat: EntityEditor class #1127
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
3373fce
refactor: moving things around, not complete
dbouwman ba4b872
feat: add EntityEditor class initial implementation
dbouwman 7ec8ab6
feat: project implementation for EntityEditor class
dbouwman cae7e53
test: partial coverage of new fns
dbouwman aec7afc
test: add coverage - one file remaining
dbouwman 4f4d03a
test: complete coverage
dbouwman cf64f9d
refactor: address review feedback
dbouwman 9c40191
refactor: ephemeral props have _ prefix
dbouwman e24f1c6
refactor: fix location default bug
dbouwman File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import { IArcGISContext } from "../ArcGISContext"; | ||
import { HubInitiative } from "../initiatives/HubInitiative"; | ||
import { HubProject } from "../projects/HubProject"; | ||
import { HubSite } from "../sites/HubSite"; | ||
import { IEditorConfig, IWithEditorBehavior } from "./behaviors"; | ||
import { getTypeFromEntity } from "./getTypeFromEntity"; | ||
import { EditorType, UiSchemaElementOptions } from "./schemas"; | ||
import { HubEntity } from "./types/HubEntity"; | ||
import { HubEntityEditor, IEntityEditorContext } from "./types/HubEntityEditor"; | ||
|
||
export class EntityEditor { | ||
instance: IWithEditorBehavior; | ||
|
||
private constructor(instance: IWithEditorBehavior) { | ||
this.instance = instance; | ||
} | ||
|
||
static fromEntity(entity: HubEntity, context: IArcGISContext): EntityEditor { | ||
const entityType = getTypeFromEntity(entity); | ||
// Create the instance and cast to EntityEditor | ||
let editor: IWithEditorBehavior; | ||
if (entityType === "project") { | ||
editor = HubProject.fromJson(entity, context) as IWithEditorBehavior; | ||
} | ||
// TODO: Uncomment as we support more entity types | ||
// if (entityType === "initiative") { | ||
// editor = HubInitiative.fromJson(entity, context) as EntityEditor; | ||
// } | ||
// if (entity.type === "site") { | ||
// editor = HubSite.fromJson(entity, context) as EntityEditor; | ||
// } | ||
if (editor) { | ||
return new EntityEditor(editor); | ||
} else { | ||
throw new Error(`Unsupported entity type: ${entity.type}`); | ||
} | ||
} | ||
|
||
async getConfig(i18nScope: string, type: EditorType): Promise<IEditorConfig> { | ||
return this.instance.getEditorConfig(i18nScope, type); | ||
} | ||
|
||
toEditor(editorContext: IEntityEditorContext = {}): HubEntityEditor { | ||
// This is ugly but it's the only way to get the type to be correct | ||
return this.instance.toEditor(editorContext) as unknown as HubEntityEditor; | ||
} | ||
|
||
async save(editor: HubEntityEditor): Promise<HubEntity> { | ||
return this.instance.fromEditor(editor); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
41 changes: 41 additions & 0 deletions
41
packages/common/src/core/schemas/internal/getCategoryItems.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { request } from "@esri/arcgis-rest-request"; | ||
import { IHubRequestOptions } from "../../../types"; | ||
import { HubEntity } from "../../types/HubEntity"; | ||
import { IUiSchemaComboboxItem } from "../types"; | ||
|
||
/** | ||
* Fetch the categorySchema for all the categories, including | ||
* the ones with 0 count, then recursively collet all the child | ||
* categories on the deepest level. Parse into a format that | ||
* can be consumed by the combobox field | ||
* | ||
* TODO: render nested categories in combobox | ||
*/ | ||
export async function getCategoryItems( | ||
orgId: string, | ||
hubRequestOptions: IHubRequestOptions | ||
): Promise<IUiSchemaComboboxItem[]> { | ||
const url = `${hubRequestOptions.portal}/portals/${orgId}/categorySchema`; | ||
try { | ||
const { categorySchema } = await request(url, hubRequestOptions); | ||
return parseCategories(categorySchema[0].categories, []); | ||
} catch (e) { | ||
return []; | ||
} | ||
} | ||
|
||
function parseCategories( | ||
categories: any[], | ||
allCategories: any[] | ||
): IUiSchemaComboboxItem[] { | ||
categories.forEach((c) => { | ||
if (!c.categories?.length) { | ||
allCategories.push({ | ||
value: c.title, | ||
}); | ||
} else { | ||
parseCategories(c.categories, allCategories); | ||
} | ||
}); | ||
return allCategories; | ||
} |
31 changes: 31 additions & 0 deletions
31
packages/common/src/core/schemas/internal/getFeaturedContentCatalogs.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { IUser } from "@esri/arcgis-rest-portal"; | ||
import { | ||
WellKnownCatalog, | ||
getWellKnownCatalog, | ||
} from "../../../search/wellKnownCatalog"; | ||
|
||
/** | ||
* Return a catalog structured for picking featured content. | ||
* @param user | ||
* @returns | ||
*/ | ||
export function getFeaturedContentCatalogs(user: IUser) { | ||
const catalogNames: WellKnownCatalog[] = [ | ||
"myContent", | ||
"favorites", | ||
"organization", | ||
"world", | ||
]; | ||
const catalogs = catalogNames.map((name: WellKnownCatalog) => { | ||
const opts = { user }; | ||
const catalog = getWellKnownCatalog( | ||
"shared.fields.featuredContent", | ||
name, | ||
"item", | ||
opts | ||
); | ||
return catalog; | ||
}); | ||
|
||
return { catalogs }; | ||
} |
14 changes: 14 additions & 0 deletions
14
packages/common/src/core/schemas/internal/getFeaturedImageUrl.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { IArcGISContext } from "../../../ArcGISContext"; | ||
import { cacheBustUrl } from "../../../urls/cacheBustUrl"; | ||
import { HubEntity } from "../../types/HubEntity"; | ||
|
||
export function getFeaturedImageUrl( | ||
entity: HubEntity, | ||
context: IArcGISContext | ||
) { | ||
const queryParams = context.isAuthenticated | ||
? `?token=${context.session.token}` | ||
: ""; | ||
// TODO: Decide if the url should be passed in or plucked out of this deep path here | ||
return cacheBustUrl(`${entity.view.featuredImageUrl}${queryParams}`); | ||
} |
16 changes: 16 additions & 0 deletions
16
packages/common/src/core/schemas/internal/getLocationExtent.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { bBoxToExtent, getGeographicOrgExtent } from "../../../extent"; | ||
import { IHubRequestOptions } from "../../../types"; | ||
import { HubEntity } from "../../types/HubEntity"; | ||
|
||
/** | ||
* Get the extent from the entity's location, if it has one. | ||
* Otherwise, fall back to using the org extent. | ||
*/ | ||
export async function getLocationExtent( | ||
entity: HubEntity, | ||
hubRequestOptions: IHubRequestOptions | ||
) { | ||
return entity.location?.extent?.length | ||
? bBoxToExtent(entity.location.extent) | ||
: await getGeographicOrgExtent(hubRequestOptions); | ||
} |
66 changes: 66 additions & 0 deletions
66
packages/common/src/core/schemas/internal/getLocationOptions.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import { extentToBBox, getGeographicOrgExtent } from "../../../extent"; | ||
import { IHubRequestOptions } from "../../../types"; | ||
import { getTypeFromEntity } from "../../getTypeFromEntity"; | ||
import { HubEntity } from "../../types/HubEntity"; | ||
import { HubEntityType } from "../../types/HubEntityType"; | ||
import { IHubLocation, IHubLocationOption } from "../../types/IHubLocation"; | ||
|
||
/** | ||
* Construct the dynamic location picker options with the entity's | ||
* configured location using the following logic: | ||
* | ||
* 1. If there is no entity (e.g. we're creating an entity), | ||
* select the "custom" option by default | ||
* 2. Else if there is no location set on the entity, select the | ||
* "none" option | ||
* 3. Else if the entity has a location and it's the same as the | ||
* current option, select it and update the option with the entity's | ||
* location | ||
*/ | ||
export async function getLocationOptions( | ||
entity: HubEntity, | ||
portalName: string, | ||
hubRequestOptions: IHubRequestOptions | ||
): Promise<IHubLocationOption[]> { | ||
const defaultExtent = await getGeographicOrgExtent(hubRequestOptions); | ||
const location: IHubLocation = entity.location; | ||
|
||
return ( | ||
[ | ||
{ | ||
label: "{{shared.fields.location.none:translate}}", | ||
location: { type: "none" }, | ||
}, | ||
{ | ||
label: "{{shared.fields.location.org:translate}}", | ||
description: portalName, | ||
location: { | ||
type: "org", | ||
extent: extentToBBox(defaultExtent), | ||
spatialReference: defaultExtent.spatialReference, | ||
}, | ||
}, | ||
{ | ||
label: "{{shared.fields.location.custom:translate}}", | ||
description: "{{shared.fields.location.customDescription:translate}}", | ||
entityType: getTypeFromEntity(entity), | ||
location: { | ||
type: "custom", | ||
spatialReference: defaultExtent.spatialReference, | ||
}, | ||
}, | ||
] as IHubLocationOption[] | ||
).map((option) => { | ||
// If this is a new entity, select the custom option by default | ||
if (!entity.id && option.location.type === "custom") { | ||
option.selected = true; | ||
} else if (entity.id && !location && option.location.type === "none") { | ||
option.selected = true; | ||
} else if (location?.type === option.location.type) { | ||
option.location = location; | ||
option.selected = true; | ||
} | ||
|
||
return option; | ||
}); | ||
} |
33 changes: 33 additions & 0 deletions
33
packages/common/src/core/schemas/internal/getSharableGroupsComboBoxItems.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { IGroup } from "@esri/arcgis-rest-portal"; | ||
import { IUiSchemaComboboxItem } from "../types"; | ||
|
||
/** | ||
* Filter an array of groups into the set the user has the rights to share content | ||
* into, and then convert them to UiSchemaComboboxItem array for use in the Entity Editor component | ||
*/ | ||
export function getSharableGroupsComboBoxItems( | ||
groups: IGroup[] | ||
): IUiSchemaComboboxItem[] { | ||
return groups.reduce((groupItems: IUiSchemaComboboxItem[], group: IGroup) => { | ||
const isEditGroup = group.capabilities.includes("updateitemcontrol"); | ||
const memberType = group.userMembership?.memberType; | ||
|
||
/** | ||
* a user can only share to a group if: | ||
* 1. the group is NOT view only | ||
* 2. their membership type in a view only group is "owner" or "admin" | ||
*/ | ||
const canShareToGroup = | ||
!group.isViewOnly || | ||
(group.isViewOnly && ["owner", "admin"].includes(memberType)); | ||
if (canShareToGroup) { | ||
groupItems.push({ | ||
value: group.id, | ||
label: group.title, | ||
icon: isEditGroup ? "unlock" : "view-mixed", | ||
}); | ||
} | ||
|
||
return groupItems; | ||
}, []); | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is a good question, and generally speaking, I feel like it would be nice to make this util more generic/less featured-image-specific. I just ran into needing similar functionality with Aaron who's trying to hook up editing an item's thumbnail. The differences there are:
entity.thumbnailUrl
entity.thumbnailUrl
already has the token appended to itSo not entirely sure how to make this generic to work in both cases, but you could do something like:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree - I'll make an issue to try to resolve this - I don't want to add additional concerns / refactors to this PR