Skip to content
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

Resource Tagging #9367

Merged
merged 26 commits into from
Dec 26, 2023
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
6cfe5c0
seed static resources with project assets
dinomut1 Dec 2, 2023
6ef7c75
licensing
dinomut1 Dec 2, 2023
1c37b1a
Merge branch 'dev' of https://github.com/EtherealEngine/etherealengin…
dinomut1 Dec 8, 2023
51e37a8
checkpoint
dinomut1 Dec 8, 2023
786cb49
Merge branch 'dev' of https://github.com/EtherealEngine/etherealengin…
dinomut1 Dec 18, 2023
8b59087
Merge branch 'dev' of https://github.com/EtherealEngine/etherealengin…
dinomut1 Dec 19, 2023
e75031b
generate .sql file for project static resources
dinomut1 Dec 20, 2023
b7576ad
working tagging UI in File Browser
dinomut1 Dec 20, 2023
a163be6
licensing
dinomut1 Dec 20, 2023
79acce9
switch from .sql to .json resources file
dinomut1 Dec 20, 2023
ff2a8ac
re-async _fetchDevLocalProjects
dinomut1 Dec 20, 2023
693071e
remove pagination
dinomut1 Dec 21, 2023
a806f40
wipe URL from manifest files
dinomut1 Dec 21, 2023
b0dd654
add check for existing id
dinomut1 Dec 21, 2023
46599b8
fix key corruption
dinomut1 Dec 21, 2023
f6d36e1
close file properties on save changes
dinomut1 Dec 21, 2023
fac2533
Merge branch 'dev' of https://github.com/EtherealEngine/etherealengin…
dinomut1 Dec 21, 2023
4bfa807
undo hardcoded visible back button
dinomut1 Dec 21, 2023
b7b7a57
increase pagination count in existing asset query
dinomut1 Dec 21, 2023
f7ff511
fix eslint errors
dinomut1 Dec 21, 2023
d08b878
use key instead of id to detect duplicate resources
dinomut1 Dec 22, 2023
e88d255
remove pagination on project-helper
dinomut1 Dec 22, 2023
f9ca92c
fix duplicate resources generated on npm run dev locally
dinomut1 Dec 22, 2023
a603f2c
use useFind in FilePropertiesPanel
dinomut1 Dec 22, 2023
b95b3f6
remove mysqldump from server package.json
dinomut1 Dec 23, 2023
1c3c939
Merge branch 'dev' into resource-tagging
dinomut1 Dec 26, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
40 changes: 5 additions & 35 deletions packages/editor/src/components/assets/FileBrowserContentPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,6 @@ import ViewInArIcon from '@mui/icons-material/ViewInAr'
import VolumeUpIcon from '@mui/icons-material/VolumeUp'

import { Engine } from '@etherealengine/engine/src/ecs/classes/Engine'
import Dialog from '@etherealengine/ui/src/primitives/mui/Dialog'
import DialogTitle from '@etherealengine/ui/src/primitives/mui/DialogTitle'
import Grid from '@etherealengine/ui/src/primitives/mui/Grid'
import Typography from '@etherealengine/ui/src/primitives/mui/Typography'

import { Breadcrumbs, Link, PopoverPosition, TablePagination } from '@mui/material'
Expand All @@ -72,6 +69,7 @@ import { archiverPath } from '@etherealengine/engine/src/schemas/media/archiver.
import { fileBrowserUploadPath } from '@etherealengine/engine/src/schemas/media/file-browser-upload.schema'
import { SupportedFileTypes } from '../../constants/AssetTypes'
import { inputFileWithAddToScene } from '../../functions/assetFunctions'
import { saveProjectResources } from '../../functions/saveProjectResources'
import { bytesToSize, unique } from '../../functions/utils'
import { Button } from '../inputs/Button'
import StringInput from '../inputs/StringInput'
Expand All @@ -80,6 +78,7 @@ import { AssetSelectionChangePropsType } from './AssetsPreviewPanel'
import CompressionPanel from './CompressionPanel'
import { FileBrowserItem } from './FileBrowserGrid'
import { FileDataType } from './FileDataType'
import { FilePropertiesPanel } from './FilePropertiesPanel'
import ImageConvertPanel from './ImageConvertPanel'
import styles from './styles.module.scss'

Expand Down Expand Up @@ -318,7 +317,7 @@ const FileBrowserContentPanel: React.FC<FileBrowserContentPanelProps> = (props)
const showUploadAndDownloadButtons =
selectedDirectory.value.slice(1).startsWith('projects/') &&
!['projects', 'projects/'].includes(selectedDirectory.value.slice(1))
const showBackButton = selectedDirectory.value !== originalPath
const showBackButton = true //selectedDirectory.value !== originalPath

const handleDownloadProject = async () => {
const url = selectedDirectory.value
Expand Down Expand Up @@ -534,6 +533,7 @@ const FileBrowserContentPanel: React.FC<FileBrowserContentPanelProps> = (props)
variant="body2"
/>
)}
<button onClick={saveProjectResources}>Save Project Resources</button>
<div id="file-browser-panel" style={{ overflowY: 'auto', height: '100%' }}>
<DndWrapper id="file-browser-panel">
<DropArea />
Expand All @@ -558,37 +558,7 @@ const FileBrowserContentPanel: React.FC<FileBrowserContentPanelProps> = (props)
)}

{openProperties.value && fileProperties.value && (
<Dialog
open={openProperties.value}
onClose={() => openProperties.set(false)}
classes={{ paper: styles.paperDialog }}
>
<DialogTitle style={{ padding: '0', textTransform: 'capitalize' }}>
{`${fileProperties.value.name} ${fileProperties.value.type == 'folder' ? 'folder' : 'file'} Properties`}
</DialogTitle>
<Grid container spacing={1} style={{ width: '100%', margin: '0' }}>
<Grid item xs={4} style={{ paddingLeft: '10px', paddingTop: '10px', width: '100%' }}>
<Typography className={styles.primaryText}>
{t('editor:layout.filebrowser.fileProperties.name')}
</Typography>
<Typography className={styles.primaryText}>
{t('editor:layout.filebrowser.fileProperties.type')}
</Typography>
<Typography className={styles.primaryText}>
{t('editor:layout.filebrowser.fileProperties.size')}
</Typography>
<Typography className={styles.primaryText}>
{t('editor:layout.filebrowser.fileProperties.url')}
</Typography>
</Grid>
<Grid item xs={8} style={{ paddingLeft: '10px', paddingTop: '10px', width: '100%' }}>
<Typography className={styles.secondaryText}>{fileProperties.value.name}</Typography>
<Typography className={styles.secondaryText}>{fileProperties.value.type}</Typography>
<Typography className={styles.secondaryText}>{fileProperties.value.size}</Typography>
<Typography className={styles.secondaryText}>{fileProperties.value.url}</Typography>
</Grid>
</Grid>
</Dialog>
<FilePropertiesPanel openProperties={openProperties} fileProperties={fileProperties} />
)}
<ConfirmDialog
open={openConfirm.value}
Expand Down
149 changes: 149 additions & 0 deletions packages/editor/src/components/assets/FilePropertiesPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/*
CPAL-1.0 License

The contents of this file are subject to the Common Public Attribution License
Version 1.0. (the "License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE.
The License is based on the Mozilla Public License Version 1.1, but Sections 14
and 15 have been added to cover use of software over a computer network and
provide for limited attribution for the Original Developer. In addition,
Exhibit A has been modified to be consistent with Exhibit B.

Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
specific language governing rights and limitations under the License.

The Original Code is Ethereal Engine.

The Original Developer is the Initial Developer. The Initial Developer of the
Original Code is the Ethereal Engine team.

All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023
Ethereal Engine. All Rights Reserved.
*/

import { Dialog, DialogTitle, Grid, Typography } from '@mui/material'
import React, { useCallback, useEffect } from 'react'

import { NO_PROXY, State, useHookstate } from '@etherealengine/hyperflux'
import { useTranslation } from 'react-i18next'

import InputText from '@etherealengine/client-core/src/common/components/InputText'
import { Engine } from '@etherealengine/engine/src/ecs/classes/Engine'
import { FileType } from './FileBrowserContentPanel'
import styles from './styles.module.scss'

import { FileBrowserService } from '@etherealengine/client-core/src/common/services/FileBrowserService'
import { StaticResourceType, staticResourcePath } from '@etherealengine/engine/src/schemas/media/static-resource.schema'
import { Button } from '../inputs/Button'

export const FilePropertiesPanel = (props: {
openProperties: State<boolean>
fileProperties: State<FileType | null>
}) => {
const { openProperties, fileProperties } = props
const { t } = useTranslation()
if (!fileProperties.value) return null

const modifiableProperties: State<FileType> = useHookstate(
JSON.parse(JSON.stringify(fileProperties.get(NO_PROXY))) as FileType
)

const isModified = useHookstate(false)

const onChange = useCallback((state: State<any>) => {
isModified.set(true)
return (e) => {
state.set(e.target.value)
}
}, [])

const onSaveChanges = useCallback(() => {
if (isModified.value) {
if (modifiableProperties.name.value != fileProperties.value!.name) {
FileBrowserService.moveContent(
fileProperties.value!.name,
modifiableProperties.name.value,
fileProperties.value!.path,
fileProperties.value!.path
)
}
if (tags.value.length > 0) {
Engine.instance.api.service(staticResourcePath).patch(id.value, {
tags: tags.value
})
}
isModified.set(false)
}
}, [])

const staticResource = useHookstate(() =>
Engine.instance.api.service(staticResourcePath).find({
query: {
key: fileProperties.value!.key
}
})
)

const tags: State<string[]> = useHookstate([])
const id: State<string> = useHookstate('')
useEffect(() => {
if (!staticResource.promised) {
const resources = JSON.parse(JSON.stringify(staticResource.data.value[0])) as StaticResourceType
resources && tags.set(resources.tags)
resources && id.set(resources.id)
}
}, [staticResource])

return (
<Dialog
open={openProperties.value}
onClose={() => openProperties.set(false)}
classes={{ paper: styles.paperDialog }}
>
<DialogTitle style={{ padding: '0', textTransform: 'capitalize' }}>
{`${fileProperties.value.name} ${fileProperties.value.type == 'folder' ? 'folder' : 'file'} Properties`}
</DialogTitle>
<form style={{ marginTop: '15px' }}>
<InputText
name="name"
label={t('editor:layout.filebrowser.fileProperties.name')}
onChange={onChange(modifiableProperties.name)}
value={modifiableProperties.name.value}
/>
<Grid container spacing={5}>
<Grid item xs={6}>
<Typography className={styles.primaryText}>{t('editor:layout.filebrowser.fileProperties.type')}</Typography>
<Typography className={styles.primaryText}>{t('editor:layout.filebrowser.fileProperties.size')}</Typography>
</Grid>
<Grid item xs={6}>
<Typography className={styles.secondaryText}>{modifiableProperties.type.value}</Typography>
<Typography className={styles.secondaryText}>{modifiableProperties.size.value}</Typography>
</Grid>
</Grid>
<Button
onClick={() => {
tags.set([...tags.value, ''])
}}
>
Add Tag
</Button>
{tags.value.map((tag, index) => (
<InputText
key={index}
name={`tag${index}`}
label={t('editor:layout.filebrowser.fileProperties.tag')}
onChange={onChange(tags[index])}
value={tags[index].value}
/>
))}
{isModified.value && (
<Button onClick={onSaveChanges} style={{ marginTop: '15px' }}>
Save Changes
</Button>
)}
</form>
</Dialog>
)
}
34 changes: 34 additions & 0 deletions packages/editor/src/functions/saveProjectResources.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
CPAL-1.0 License

The contents of this file are subject to the Common Public Attribution License
Version 1.0. (the "License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE.
The License is based on the Mozilla Public License Version 1.1, but Sections 14
and 15 have been added to cover use of software over a computer network and
provide for limited attribution for the Original Developer. In addition,
Exhibit A has been modified to be consistent with Exhibit B.

Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
specific language governing rights and limitations under the License.

The Original Code is Ethereal Engine.

The Original Developer is the Initial Developer. The Initial Developer of the
Original Code is the Ethereal Engine team.

All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023
Ethereal Engine. All Rights Reserved.
*/

import { Engine } from '@etherealengine/engine/src/ecs/classes/Engine'
import { getState } from '@etherealengine/hyperflux'
import { EditorState } from '../services/EditorServices'

export async function saveProjectResources() {
const project = getState(EditorState).projectName!

await Engine.instance.api.service('project-resources').create({ project })
}
5 changes: 4 additions & 1 deletion packages/engine/src/schemas/media/static-resource.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export const staticResourceQueryProperties = Type.Pick(staticResourceSchema, [
'driver',
'attribution',
'licensing',
// 'tags',
'tags',
'url'
// 'stats'
])
Expand All @@ -103,6 +103,9 @@ export const staticResourceQuerySchema = Type.Intersect(
},
mimeType: {
$like: Type.String()
},
tags: {
$like: Type.String()
}
}),
// Add additional query properties here
Expand Down
1 change: 1 addition & 0 deletions packages/server-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@
"multer": "1.4.5-lts.1",
"mysql": "2.18.1",
"mysql2": "3.2.0",
"mysqldump": "3.2.0",
"nanoid": "3.3.4",
"node-fetch": "2.6.9",
"nodemailer-smtp-transport": "^2.7.4",
Expand Down
12 changes: 11 additions & 1 deletion packages/server-core/src/media/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,17 @@ import FileBrowser from './file-browser/file-browser'
import OEmbed from './oembed/oembed'
import Archiver from './recursive-archiver/archiver'
import StaticResourceFilters from './static-resource-filters/static-resource-filters'
import ProjectResource from './static-resource/project-resource.service'
import StaticResource from './static-resource/static-resource'
import Upload from './upload-asset/upload-asset.service'

export default [StaticResource, StaticResourceFilters, FileBrowser, FileBrowserUpload, OEmbed, Upload, Archiver]
export default [
ProjectResource,
StaticResource,
StaticResourceFilters,
FileBrowser,
FileBrowserUpload,
OEmbed,
Upload,
Archiver
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
CPAL-1.0 License

The contents of this file are subject to the Common Public Attribution License
Version 1.0. (the "License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE.
The License is based on the Mozilla Public License Version 1.1, but Sections 14
and 15 have been added to cover use of software over a computer network and
provide for limited attribution for the Original Developer. In addition,
Exhibit A has been modified to be consistent with Exhibit B.

Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
specific language governing rights and limitations under the License.

The Original Code is Ethereal Engine.

The Original Developer is the Initial Developer. The Initial Developer of the
Original Code is the Ethereal Engine team.

All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023
Ethereal Engine. All Rights Reserved.
*/

import { StaticResourceType, staticResourcePath } from '@etherealengine/engine/src/schemas/media/static-resource.schema'
import { Application } from '@feathersjs/koa'

import { getStorageProvider } from '../storageprovider/storageprovider'

export const projectResourcesPath = 'project-resources'

export type CreateProjectResourceParams = {
project: string
}

declare module '@etherealengine/common/declarations' {
interface ServiceTypes {
[projectResourcesPath]: any
}
}

const createProjectResource =
(app: Application) =>
async ({ project }: CreateProjectResourceParams) => {
const resources = await app.service(staticResourcePath).find({
query: { project }
})
const data: StaticResourceType[] = resources.data
const storageProvider = getStorageProvider()

await storageProvider.putObject({
Body: Buffer.from(JSON.stringify(data)),
ContentType: 'application/json',
Key: `projects/${project}/resources.json`
})
}

export default (app: Application): void => {
app.use(projectResourcesPath, {
create: createProjectResource(app)
})
}