feat: file manager improvements + tree db model

parent c377eca6
......@@ -12,6 +12,9 @@ const reTitle = /^[^<>"]+$/
module.exports = {
Query: {
/**
* FETCH TREE
*/
async tree (obj, args, context, info) {
// Offset
const offset = args.offset || 0
......@@ -20,8 +23,8 @@ module.exports = {
}
// Limit
const limit = args.limit || 100
if (limit < 1 || limit > 100) {
const limit = args.limit || 1000
if (limit < 1 || limit > 1000) {
throw new Error('Invalid Limit')
}
......@@ -53,17 +56,27 @@ module.exports = {
.select(WIKI.db.knex.raw('tree.*, nlevel(tree."folderPath") AS depth'))
.where(builder => {
builder.where('folderPath', '~', folderPathCondition)
// -> Include ancestors
if (args.includeAncestors) {
const parentPathParts = parentPath.split('.')
for (let i = 1; i <= parentPathParts.length; i++) {
builder.orWhere({
folderPath: _.dropRight(parentPathParts, i).join('.'),
fileName: _.nth(parentPathParts, i * -1)
fileName: _.nth(parentPathParts, i * -1),
type: 'folder'
})
}
}
// -> Include root items
if (args.includeRootItems) {
builder.orWhere({
folderPath: '',
type: 'folder'
})
}
})
.andWhere(builder => {
// -> Limit to specific types
if (args.types && args.types.length > 0) {
builder.whereIn('type', args.types)
}
......@@ -85,71 +98,63 @@ module.exports = {
createdAt: item.createdAt,
updatedAt: item.updatedAt,
...(item.type === 'folder') && {
childrenCount: 0
childrenCount: item.meta?.children || 0
}
}))
},
/**
* FETCH SINGLE FOLDER BY ID
*/
async folderById (obj, args, context) {
const folder = await WIKI.db.knex('tree')
.select(WIKI.db.knex.raw('tree.*, nlevel(tree."folderPath") AS depth'))
.where('id', args.id)
.first()
if (!folder) {
throw new Error('ERR_FOLDER_NOT_EXIST')
}
return {
...folder,
folderPath: folder.folderPath.replaceAll('.', '/').replaceAll('_', '-'),
childrenCount: 0
}
childrenCount: folder.meta?.children || 0
}
},
Mutation: {
/**
* CREATE FOLDER
* FETCH SINGLE FOLDER BY PATH
*/
async createFolder (obj, args, context) {
try {
WIKI.logger.debug(`Creating new folder ${args.pathName}...`)
// Get parent path
let parentPath = ''
if (args.parentId) {
const parent = await WIKI.db.knex('tree').where('id', args.parentId).first()
parentPath = parent ? `${parent.folderPath}.${parent.fileName}` : ''
if (parent) {
parentPath = parent.folderPath ? `${parent.folderPath}.${parent.fileName}` : parent.fileName
}
parentPath = parentPath.replaceAll('-', '_')
}
async folderByPath (obj, args, context) {
const parentPathParts = args.path.replaceAll('/', '.').replaceAll('-', '_').split('.')
const folder = await WIKI.db.knex('tree')
.select(WIKI.db.knex.raw('tree.*, nlevel(tree."folderPath") AS depth'))
.where({
siteId: args.siteId,
localeCode: args.locale,
folderPath: _.dropRight(parentPathParts).join('.'),
fileName: _.last(parentPathParts)
})
.first()
// Validate path name
if (!rePathName.test(args.pathName)) {
throw new Error('ERR_INVALID_PATH_NAME')
if (!folder) {
throw new Error('ERR_FOLDER_NOT_EXIST')
}
// Validate title
if (!reTitle.test(args.title)) {
throw new Error('ERR_INVALID_TITLE')
return {
...folder,
folderPath: folder.folderPath.replaceAll('.', '/').replaceAll('_', '-'),
childrenCount: folder.meta?.children || 0
}
// Check for collision
const existingFolder = await WIKI.db.knex('tree').where({
siteId: args.siteId,
folderPath: parentPath,
fileName: args.pathName
}).first()
if (existingFolder) {
throw new Error('ERR_FOLDER_ALREADY_EXISTS')
}
},
Mutation: {
/**
* CREATE FOLDER
*/
async createFolder (obj, args, context) {
try {
await WIKI.db.tree.createFolder(args)
// Create folder
WIKI.logger.debug(`Creating new folder ${args.pathName} at path /${parentPath}...`)
await WIKI.db.knex('tree').insert({
folderPath: parentPath,
fileName: args.pathName,
type: 'folder',
title: args.title,
siteId: args.siteId
})
return {
operation: graphHelper.generateSuccess('Folder created successfully')
}
......@@ -163,59 +168,7 @@ module.exports = {
*/
async renameFolder (obj, args, context) {
try {
// Get folder
const folder = await WIKI.db.knex('tree').where('id', args.folderId).first()
WIKI.logger.debug(`Renaming folder ${folder.id} path to ${args.pathName}...`)
// Validate path name
if (!rePathName.test(args.pathName)) {
throw new Error('ERR_INVALID_PATH_NAME')
}
// Validate title
if (!reTitle.test(args.title)) {
throw new Error('ERR_INVALID_TITLE')
}
if (args.pathName !== folder.fileName) {
// Check for collision
const existingFolder = await WIKI.db.knex('tree')
.whereNot('id', folder.id)
.andWhere({
siteId: folder.siteId,
folderPath: folder.folderPath,
fileName: args.pathName
}).first()
if (existingFolder) {
throw new Error('ERR_FOLDER_ALREADY_EXISTS')
}
// Build new paths
const oldFolderPath = (folder.folderPath ? `${folder.folderPath}.${folder.fileName}` : folder.fileName).replaceAll('-', '_')
const newFolderPath = (folder.folderPath ? `${folder.folderPath}.${args.pathName}` : args.pathName).replaceAll('-', '_')
// Update children nodes
WIKI.logger.debug(`Updating parent path of children nodes from ${oldFolderPath} to ${newFolderPath} ...`)
await WIKI.db.knex('tree').where('siteId', folder.siteId).andWhere('folderPath', oldFolderPath).update({
folderPath: newFolderPath
})
await WIKI.db.knex('tree').where('siteId', folder.siteId).andWhere('folderPath', '<@', oldFolderPath).update({
folderPath: WIKI.db.knex.raw(`'${newFolderPath}' || subpath(tree."folderPath", nlevel('${newFolderPath}'))`)
})
// Rename the folder itself
await WIKI.db.knex('tree').where('id', folder.id).update({
fileName: args.pathName,
title: args.title
})
} else {
// Update the folder title only
await WIKI.db.knex('tree').where('id', folder.id).update({
title: args.title
})
}
WIKI.logger.debug(`Renamed folder ${folder.id} successfully.`)
await WIKI.db.tree.renameFolder(args)
return {
operation: graphHelper.generateSuccess('Folder renamed successfully')
......@@ -230,39 +183,7 @@ module.exports = {
*/
async deleteFolder (obj, args, context) {
try {
// Get folder
const folder = await WIKI.db.knex('tree').where('id', args.folderId).first()
const folderPath = folder.folderPath ? `${folder.folderPath}.${folder.fileName}` : folder.fileName
WIKI.logger.debug(`Deleting folder ${folder.id} at path ${folderPath}...`)
// Delete all children
const deletedNodes = await WIKI.db.knex('tree').where('folderPath', '<@', folderPath).del().returning(['id', 'type'])
// Delete folders
const deletedFolders = deletedNodes.filter(n => n.type === 'folder').map(n => n.id)
if (deletedFolders.length > 0) {
WIKI.logger.debug(`Deleted ${deletedFolders.length} children folders.`)
}
// Delete pages
const deletedPages = deletedNodes.filter(n => n.type === 'page').map(n => n.id)
if (deletedPages.length > 0) {
WIKI.logger.debug(`Deleting ${deletedPages.length} children pages...`)
// TODO: Delete page
}
// Delete assets
const deletedAssets = deletedNodes.filter(n => n.type === 'asset').map(n => n.id)
if (deletedAssets.length > 0) {
WIKI.logger.debug(`Deleting ${deletedPages.length} children assets...`)
// TODO: Delete asset
}
// Delete the folder itself
await WIKI.db.knex('tree').where('id', folder.id).del()
WIKI.logger.debug(`Deleted folder ${folder.id} successfully.`)
await WIKI.db.tree.deleteFolder(args.folderId)
return {
operation: graphHelper.generateSuccess('Folder deleted successfully')
......
......@@ -14,16 +14,24 @@ extend type Query {
orderByDirection: OrderByDirection
depth: Int
includeAncestors: Boolean
includeRootItems: Boolean
): [TreeItem]
folderById(
id: UUID!
): TreeItemFolder
folderByPath(
siteId: UUID!
locale: String!
path: String!
): TreeItemFolder
}
extend type Mutation {
createFolder(
siteId: UUID!
locale: String!
parentId: UUID
parentPath: String
pathName: String!
title: String!
): DefaultResponse
......
const Model = require('objection').Model
const _ = require('lodash')
const rePathName = /^[a-z0-9-]+$/
const reTitle = /^[^<>"]+$/
/**
* Tree model
*/
module.exports = class Tree extends Model {
static get tableName() { return 'tree' }
static get jsonSchema () {
return {
type: 'object',
required: ['fileName'],
properties: {
id: {type: 'string'},
folderPath: {type: 'string'},
fileName: {type: 'string'},
type: {type: 'string'},
title: {type: 'string'},
createdAt: {type: 'string'},
updatedAt: {type: 'string'}
}
}
}
static get jsonAttributes() {
return ['meta']
}
static get relationMappings() {
return {
locale: {
relation: Model.BelongsToOneRelation,
modelClass: require('./locales'),
join: {
from: 'tree.localeCode',
to: 'locales.code'
}
},
site: {
relation: Model.BelongsToOneRelation,
modelClass: require('./sites'),
join: {
from: 'tree.siteId',
to: 'sites.id'
}
}
}
}
$beforeUpdate() {
this.updatedAt = new Date().toISOString()
}
$beforeInsert() {
this.createdAt = new Date().toISOString()
this.updatedAt = new Date().toISOString()
}
/**
* Create New Folder
*
* @param {Object} args - New Folder Properties
* @param {string} [args.parentId] - UUID of the parent folder
* @param {string} [args.parentPath] - Path of the parent folder
* @param {string} args.pathName - Path name of the folder to create
* @param {string} args.title - Title of the folder to create
* @param {string} args.locale - Locale code of the folder to create
* @param {string} args.siteId - UUID of the site in which the folder will be created
*/
static async createFolder ({ parentId, parentPath, pathName, title, locale, siteId }) {
// Validate path name
if (!rePathName.test(pathName)) {
throw new Error('ERR_INVALID_PATH_NAME')
}
// Validate title
if (!reTitle.test(title)) {
throw new Error('ERR_INVALID_TITLE')
}
WIKI.logger.debug(`Creating new folder ${pathName}...`)
parentPath = parentPath?.replaceAll('/', '.')?.replaceAll('-', '_') || ''
const parentPathParts = parentPath.split('.')
const parentFilter = {
folderPath: _.dropRight(parentPathParts).join('.'),
fileName: _.last(parentPathParts)
}
// Get parent path
let parent = null
if (parentId) {
parent = await WIKI.db.knex('tree').where('id', parentId).first()
if (!parent) {
throw new Error('ERR_NONEXISTING_PARENT_ID')
}
parentPath = parent.folderPath ? `${parent.folderPath}.${parent.fileName}` : parent.fileName
} else if (parentPath) {
parent = await WIKI.db.knex('tree').where(parentFilter).first()
} else {
parentPath = ''
}
// Check for collision
const existingFolder = await WIKI.db.knex('tree').where({
siteId: siteId,
localeCode: locale,
folderPath: parentPath,
fileName: pathName
}).first()
if (existingFolder) {
throw new Error('ERR_FOLDER_ALREADY_EXISTS')
}
// Ensure all ancestors exist
if (parentPath) {
const expectedAncestors = []
const existingAncestors = await WIKI.db.knex('tree').select('folderPath', 'fileName').where(builder => {
const parentPathParts = parentPath.split('.')
for (let i = 1; i <= parentPathParts.length; i++) {
const ancestor = {
folderPath: _.dropRight(parentPathParts, i).join('.'),
fileName: _.nth(parentPathParts, i * -1)
}
expectedAncestors.push(ancestor)
builder.orWhere({
...ancestor,
type: 'folder'
})
}
})
for (const ancestor of _.differenceWith(expectedAncestors, existingAncestors, (expAnc, exsAnc) => expAnc.folderPath === exsAnc.folderPath && expAnc.fileName === exsAnc.fileName)) {
WIKI.logger.debug(`Creating missing parent folder ${ancestor.fileName} at path /${ancestor.folderPath}...`)
const newAncestor = await WIKI.db.knex('tree').insert({
...ancestor,
type: 'folder',
title: ancestor.fileName,
localeCode: locale,
siteId: siteId,
meta: {
children: 1
}
}).returning('*')
// Parent didn't exist until now, assign it
if (!parent && ancestor.folderPath === parentFilter.folderPath && ancestor.fileName === parentFilter.fileName) {
parent = newAncestor
}
}
}
// Create folder
WIKI.logger.debug(`Creating new folder ${pathName} at path /${parentPath}...`)
await WIKI.db.knex('tree').insert({
folderPath: parentPath,
fileName: pathName,
type: 'folder',
title: title,
localeCode: locale,
siteId: siteId,
meta: {
children: 0
}
})
// Update parent ancestor count
if (parent) {
await WIKI.db.knex('tree').where('id', parent.id).update({
meta: {
...(parent.meta ?? {}),
children: (parent.meta?.children || 0) + 1
}
})
}
}
/**
* Rename a folder
*
* @param {Object} args - Rename Folder Properties
* @param {string} args.folderId - UUID of the folder to rename
* @param {string} args.pathName - New path name of the folder
* @param {string} args.title - New title of the folder
*/
static async renameFolder ({ folderId, pathName, title }) {
// Get folder
const folder = await WIKI.db.knex('tree').where('id', folderId).first()
if (!folder) {
throw new Error('ERR_NONEXISTING_FOLDER_ID')
}
// Validate path name
if (!rePathName.test(pathName)) {
throw new Error('ERR_INVALID_PATH_NAME')
}
// Validate title
if (!reTitle.test(title)) {
throw new Error('ERR_INVALID_TITLE')
}
WIKI.logger.debug(`Renaming folder ${folder.id} path to ${pathName}...`)
if (pathName !== folder.fileName) {
// Check for collision
const existingFolder = await WIKI.db.knex('tree')
.whereNot('id', folder.id)
.andWhere({
siteId: folder.siteId,
folderPath: folder.folderPath,
fileName: pathName
}).first()
if (existingFolder) {
throw new Error('ERR_FOLDER_ALREADY_EXISTS')
}
// Build new paths
const oldFolderPath = (folder.folderPath ? `${folder.folderPath}.${folder.fileName}` : folder.fileName).replaceAll('-', '_')
const newFolderPath = (folder.folderPath ? `${folder.folderPath}.${pathName}` : pathName).replaceAll('-', '_')
// Update children nodes
WIKI.logger.debug(`Updating parent path of children nodes from ${oldFolderPath} to ${newFolderPath} ...`)
await WIKI.db.knex('tree').where('siteId', folder.siteId).andWhere('folderPath', oldFolderPath).update({
folderPath: newFolderPath
})
await WIKI.db.knex('tree').where('siteId', folder.siteId).andWhere('folderPath', '<@', oldFolderPath).update({
folderPath: WIKI.db.knex.raw(`'${newFolderPath}' || subpath(tree."folderPath", nlevel('${newFolderPath}'))`)
})
// Rename the folder itself
await WIKI.db.knex('tree').where('id', folder.id).update({
fileName: pathName,
title: title
})
} else {
// Update the folder title only
await WIKI.db.knex('tree').where('id', folder.id).update({
title: title
})
}
WIKI.logger.debug(`Renamed folder ${folder.id} successfully.`)
}
/**
* Delete a folder
*
* @param {String} folderId Folder ID
*/
static async deleteFolder (folderId) {
// Get folder
const folder = await WIKI.db.knex('tree').where('id', folderId).first()
if (!folder) {
throw new Error('ERR_NONEXISTING_FOLDER_ID')
}
const folderPath = folder.folderPath ? `${folder.folderPath}.${folder.fileName}` : folder.fileName
WIKI.logger.debug(`Deleting folder ${folder.id} at path ${folderPath}...`)
// Delete all children
const deletedNodes = await WIKI.db.knex('tree').where('folderPath', '<@', folderPath).del().returning(['id', 'type'])
// Delete folders
const deletedFolders = deletedNodes.filter(n => n.type === 'folder').map(n => n.id)
if (deletedFolders.length > 0) {
WIKI.logger.debug(`Deleted ${deletedFolders.length} children folders.`)
}
// Delete pages
const deletedPages = deletedNodes.filter(n => n.type === 'page').map(n => n.id)
if (deletedPages.length > 0) {
WIKI.logger.debug(`Deleting ${deletedPages.length} children pages...`)
// TODO: Delete page
}
// Delete assets
const deletedAssets = deletedNodes.filter(n => n.type === 'asset').map(n => n.id)
if (deletedAssets.length > 0) {
WIKI.logger.debug(`Deleting ${deletedPages.length} children assets...`)
// TODO: Delete asset
}
// Delete the folder itself
await WIKI.db.knex('tree').where('id', folder.id).del()
// Update parent children count
if (folder.folderPath) {
const parentPathParts = folder.folderPath.split('.')
const parent = await WIKI.db.knex('tree').where({
folderPath: _.dropRight(parentPathParts).join('.'),
fileName: _.last(parentPathParts)
}).first()
await WIKI.db.knex('tree').where('id', parent.id).update({
meta: {
...(parent.meta ?? {}),
children: (parent.meta?.children || 1) - 1
}
})
}
WIKI.logger.debug(`Deleted folder ${folder.id} successfully.`)
}
}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="100px" height="100px"><path d="M 18 27 A 1.0001 1.0001 0 0 0 17 28 L 17 75 C 17 80.511334 21.488666 85 27 85 L 72 85 C 77.511334 85 82 80.511334 82 75 L 82 28 A 1.0001 1.0001 0 0 0 81 27 L 18 27 z M 19 29 L 49 29 L 49 36 L 39.5 36 C 38.125015 36 37 37.125015 37 38.5 L 37 40.5 C 37 41.874985 38.125015 43 39.5 43 L 49 43 L 49 78 L 30.5 78 C 26.904219 78 24 75.095781 24 71.5 L 24 32.5 A 0.50005 0.50005 0 0 0 23.492188 31.992188 A 0.50005 0.50005 0 0 0 23 32.5 L 23 71.5 C 23 75.636219 26.363781 79 30.5 79 L 49.419922 79 A 0.50005 0.50005 0 0 0 49.582031 79 L 68.5 79 C 72.636219 79 76 75.636219 76 71.5 L 76 43.5 A 0.50005 0.50005 0 1 0 75 43.5 L 75 71.5 C 75 75.095781 72.095781 78 68.5 78 L 50 78 L 50 43 L 59.5 43 C 60.874985 43 62 41.874985 62 40.5 L 62 38.5 C 62 37.125015 60.874985 36 59.5 36 L 50 36 L 50 29 L 80 29 L 80 75 C 80 79.430666 76.430666 83 72 83 L 27 83 C 22.569334 83 19 79.430666 19 75 L 19 29 z M 75.492188 31.992188 A 0.50005 0.50005 0 0 0 75 32.5 L 75 38.5 A 0.50005 0.50005 0 1 0 76 38.5 L 76 32.5 A 0.50005 0.50005 0 0 0 75.492188 31.992188 z M 39.5 37 L 59.5 37 C 60.335015 37 61 37.664985 61 38.5 L 61 40.5 C 61 41.335015 60.335015 42 59.5 42 L 49.580078 42 A 0.50005 0.50005 0 0 0 49.417969 42 L 39.5 42 C 38.664985 42 38 41.335015 38 40.5 L 38 38.5 C 38 37.664985 38.664985 37 39.5 37 z M 31.492188 60.992188 A 0.50005 0.50005 0 0 0 31.097656 61.195312 L 29.146484 63.146484 A 0.50005 0.50005 0 1 0 29.853516 63.853516 L 31 62.707031 L 31 68.5 A 0.50005 0.50005 0 1 0 32 68.5 L 32 62.707031 L 33.146484 63.853516 A 0.50005 0.50005 0 1 0 33.853516 63.146484 L 31.898438 61.191406 A 0.50005 0.50005 0 0 0 31.492188 60.992188 z M 38.492188 60.992188 A 0.50005 0.50005 0 0 0 38.097656 61.195312 L 36.146484 63.146484 A 0.50005 0.50005 0 1 0 36.853516 63.853516 L 38 62.707031 L 38 68.5 A 0.50005 0.50005 0 1 0 39 68.5 L 39 62.707031 L 40.146484 63.853516 A 0.50005 0.50005 0 1 0 40.853516 63.146484 L 38.898438 61.191406 A 0.50005 0.50005 0 0 0 38.492188 60.992188 z M 28.5 70 A 0.50005 0.50005 0 1 0 28.5 71 L 41.5 71 A 0.50005 0.50005 0 1 0 41.5 70 L 28.5 70 z"/></svg>
\ No newline at end of file
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 48 48" width="96px" height="96px">
<defs>
<linearGradient id="p0leOTPLvuNkjL_fSa~qVa" x1="24" x2="24" y1="9.109" y2="13.568" data-name="Безымянный градиент 6" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#0077d2"/>
<stop offset="1" stop-color="#0b59a2"/>
</linearGradient>
<linearGradient id="p0leOTPLvuNkjL_fSa~qVb" x1="4.5" x2="4.5" y1="26.717" y2="41.786" xlink:href="#p0leOTPLvuNkjL_fSa~qVa"/>
<linearGradient id="p0leOTPLvuNkjL_fSa~qVc" x1="43.5" x2="43.5" y1="26.717" y2="41.786" xlink:href="#p0leOTPLvuNkjL_fSa~qVa"/>
<linearGradient id="p0leOTPLvuNkjL_fSa~qVd" x1="16" x2="16" y1="25.054" y2="43.495" xlink:href="#p0leOTPLvuNkjL_fSa~qVa"/>
<linearGradient id="p0leOTPLvuNkjL_fSa~qVe" x1="32" x2="32" y1="25.054" y2="43.495" xlink:href="#p0leOTPLvuNkjL_fSa~qVa"/>
</defs>
<rect width="2" height="6" x="23" y="8" fill="url(#p0leOTPLvuNkjL_fSa~qVa)"/>
<path fill="url(#p0leOTPLvuNkjL_fSa~qVb)" d="M6,27H8a0,0,0,0,1,0,0V37a0,0,0,0,1,0,0H6a5,5,0,0,1-5-5v0A5,5,0,0,1,6,27Z"/>
<path fill="url(#p0leOTPLvuNkjL_fSa~qVc)" d="M40,27h2a5,5,0,0,1,5,5v0a5,5,0,0,1-5,5H40a0,0,0,0,1,0,0V27A0,0,0,0,1,40,27Z"/>
<path fill="#199be2" d="M24,13h0A18,18,0,0,1,42,31v8a2,2,0,0,1-2,2H8a2,2,0,0,1-2-2V31A18,18,0,0,1,24,13Z"/>
<circle cx="16" cy="31" r="6" fill="url(#p0leOTPLvuNkjL_fSa~qVd)"/>
<circle cx="32" cy="31" r="6" fill="url(#p0leOTPLvuNkjL_fSa~qVe)"/>
<circle cx="32" cy="31" r="4" fill="#50e6ff"/>
<circle cx="32" cy="31" r="2" fill="url(#p0leOTPLvuNkjL_fSa~qVe)"/>
<circle cx="16" cy="31" r="4" fill="#50e6ff"/>
<circle cx="16" cy="31" r="2" fill="url(#p0leOTPLvuNkjL_fSa~qVd)"/>
<circle cx="24" cy="8" r="2" fill="#199be2"/>
<circle cx="24" cy="8" r="3" fill="#02C39A">
<animate attributeName="opacity" dur="1.5s" values="0;1;0;0" repeatCount="indefinite" begin="0.1" />
</circle>
<circle cx="24" cy="8" r="3" fill="#f99d4d">
<animate attributeName="opacity" dur="1.5s" values="0;0;1;0" repeatCount="indefinite" begin="0.1" />
</circle>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" data-name="Layer 1" width="865.57286" height="658.54932" viewBox="0 0 865.57286 658.54932" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M475.13088,273.95982a1.42065,1.42065,0,0,1-1.4157-1.32118,50.21123,50.21123,0,0,0-49.94074-46.52628c-1.51455,0-3.10558.077-4.72921.22892A1.42019,1.42019,0,0,1,417.68,225.633a59.66227,59.66227,0,0,0-109.10474,12.97625,1.41413,1.41413,0,0,1-1.39713,1.025l-.37773-.01145c-.1965-.00659-.39282-.01352-.59087-.01352a42.07839,42.07839,0,0,0-41.00433,33.223,1.42071,1.42071,0,0,1-1.3902,1.12763H168.63429a1.42073,1.42073,0,0,1,0-2.84145H262.678a44.94513,44.94513,0,0,1,43.48245-34.35058,62.50318,62.50318,0,0,1,113.52733-13.33317c1.39749-.10891,2.76844-.16371,4.08666-.16371a53.062,53.062,0,0,1,52.77525,49.16864,1.42061,1.42061,0,0,1-1.31788,1.5168C475.198,273.95878,475.16435,273.95982,475.13088,273.95982Z" transform="translate(-167.21357 -120.72534)" fill="#cbcbcb"/><path d="M458.227,292.176H225.22826a1.42072,1.42072,0,1,1,0-2.84144H458.227a1.42072,1.42072,0,1,1,0,2.84144Z" transform="translate(-167.21357 -120.72534)" fill="#cbcbcb"/><path d="M282.05722,312.77652H203.9174a1.42073,1.42073,0,1,1,0-2.84145h78.13982a1.42073,1.42073,0,0,1,0,2.84145Z" transform="translate(-167.21357 -120.72534)" fill="#cbcbcb"/><path d="M355.11428,589.21139c0,57.75962-60.55444,85.39729-60.55444,85.39729s-61.17551-27.63767-62.41765-85.39729c-.73011-33.95,27.52824-61.48605,61.486-61.48605A61.486,61.486,0,0,1,355.11428,589.21139Z" transform="translate(-167.21357 -120.72534)" fill="#e5e5e5"/><polygon points="147.842 557.299 106.851 557.299 101.882 537.425 152.81 537.425 147.842 557.299" fill="#cbcbcb"/><rect x="108.24833" y="591.28757" width="38.19588" height="19.90489" fill="#fff"/><path d="M313.96832,732.22835h-38.817v-20.526h38.817Zm-38.19588-.62107h37.57481V712.32344H275.77244Z" transform="translate(-167.21357 -120.72534)" fill="#cbcbcb"/><polygon points="135.176 559.643 119.517 559.643 117.72 556.819 136.973 556.819 135.176 559.643" fill="#cbcbcb"/><rect x="107.16146" y="557.29923" width="0.62107" height="30.43249" fill="#cbcbcb"/><rect x="146.91002" y="557.29923" width="0.62107" height="30.43249" fill="#cbcbcb"/><path d="M300.78526,657.81368l-.37922-.49189c17.94222-13.83627,39.33014-37.54008,39.33014-71.89a61.09179,61.09179,0,0,0-37.36337-56.36812l.242-.572a61.71164,61.71164,0,0,1,37.74244,56.94007C340.35725,620.034,318.8378,643.89219,300.78526,657.81368Z" transform="translate(-167.21357 -120.72534)" fill="#cbcbcb"/><path d="M280.91076,657.81383l-.37923-.49189c17.9423-13.83627,39.33037-37.54008,39.33037-71.8902A61.16369,61.16369,0,0,0,282.924,529.24542l.24624-.57012A61.78432,61.78432,0,0,1,320.483,585.43174C320.483,620.03417,298.96344,643.89249,280.91076,657.81383Z" transform="translate(-167.21357 -120.72534)" fill="#cbcbcb"/><path d="M306.34549,657.81368c-18.05254-13.92134-39.572-37.77951-39.572-72.38194a61.78407,61.78407,0,0,1,37.31325-56.7566l.24624.57013a61.16357,61.16357,0,0,0-36.93842,56.18647c0,34.35,21.38791,58.05378,39.33014,71.89Z" transform="translate(-167.21357 -120.72534)" fill="#cbcbcb"/><path d="M286.47128,657.81368c-18.05261-13.92134-39.57206-37.77936-39.57206-72.38194a61.66876,61.66876,0,0,1,37.99459-57.04606l.23943.57316a61.04905,61.04905,0,0,0-37.613,56.4729c0,34.35027,21.388,58.05393,39.33022,71.89Z" transform="translate(-167.21357 -120.72534)" fill="#cbcbcb"/><rect x="126.10413" y="407" width="0.62107" height="130.42495" fill="#cbcbcb"/><line x1="108.24833" y1="598.38284" x2="119.63694" y2="586.99424" fill="#fff"/><rect x="273.10322" y="713.10337" width="16.10597" height="0.621" transform="translate(-589.32461 287.0362) rotate(-45)" fill="#cbcbcb"/><line x1="108.24833" y1="604.60334" x2="125.17986" y2="586.80011" fill="#fff"/><rect x="271.64323" y="716.11655" width="24.56889" height="0.62099" transform="translate(-598.12788 307.80663) rotate(-46.44446)" fill="#cbcbcb"/><rect x="103.43503" y="586.80011" width="46.58034" height="5.58964" fill="#fff"/><path d="M317.53948,713.42563H270.33807v-6.21071h47.20141Zm-46.58034-.62107H316.9184V707.836H270.95914Z" transform="translate(-167.21357 -120.72534)" fill="#cbcbcb"/><path d="M472.30444,442.83091c0,38.12387-39.96858,56.36594-39.96858,56.36594s-40.37851-18.24207-41.19838-56.36594c-.4819-22.40846,18.16984-40.58348,40.58348-40.58348A40.58348,40.58348,0,0,1,472.30444,442.83091Z" transform="translate(-167.21357 -120.72534)" fill="#e5e5e5"/><polygon points="278.65 380.726 251.594 380.726 248.315 367.608 281.93 367.608 278.65 380.726" fill="#cbcbcb"/><rect x="252.51682" y="403.15993" width="25.21095" height="13.1381" fill="#fff"/><path d="M445.1463,537.22835H419.52542v-13.548H445.1463Zm-25.21095-.40994h24.801V524.09024h-24.801Z" transform="translate(-167.21357 -120.72534)" fill="#cbcbcb"/><polygon points="270.29 382.273 259.954 382.273 258.768 380.409 271.476 380.409 270.29 382.273" fill="#cbcbcb"/><rect x="251.79943" y="380.72614" width="0.40993" height="20.08677" fill="#cbcbcb"/><rect x="278.03522" y="380.72614" width="0.40993" height="20.08677" fill="#cbcbcb"/><path d="M436.44491,488.11141l-.2503-.32466c11.84265-9.13254,25.9596-24.7781,25.9596-47.45058a40.32325,40.32325,0,0,0-24.66145-37.20542l.15973-.37751a40.73238,40.73238,0,0,1,24.91166,37.58293C462.56415,463.17519,448.36037,478.92262,436.44491,488.11141Z" transform="translate(-167.21357 -120.72534)" fill="#cbcbcb"/><path d="M423.32687,488.11151l-.25031-.32466c11.84271-9.13254,25.95976-24.7781,25.95976-47.45068a40.3707,40.3707,0,0,0-24.38062-37.08542l.16253-.37631a40.78035,40.78035,0,0,1,24.628,37.46173C449.44626,463.17529,435.24243,478.92282,423.32687,488.11151Z" transform="translate(-167.21357 -120.72534)" fill="#cbcbcb"/><path d="M440.1149,488.11141c-11.91546-9.18869-26.11924-24.93612-26.11924-47.77524A40.78018,40.78018,0,0,1,438.624,402.87434l.16253.37631a40.37063,40.37063,0,0,0-24.381,37.08552c0,22.67248,14.117,38.318,25.95961,47.45058Z" transform="translate(-167.21357 -120.72534)" fill="#cbcbcb"/><path d="M426.99706,488.11141c-11.91551-9.18869-26.11929-24.936-26.11929-47.77524a40.704,40.704,0,0,1,25.07809-37.65288l.158.3783a40.295,40.295,0,0,0-24.82618,37.27458c0,22.67268,14.117,38.31814,25.95965,47.45058Z" transform="translate(-167.21357 -120.72534)" fill="#cbcbcb"/><rect x="264.30242" y="281.52209" width="0.40993" height="86.08616" fill="#cbcbcb"/><line x1="252.51682" y1="407.84313" x2="260.0338" y2="400.32615" fill="#fff"/><rect x="418.17355" y="524.60503" width="10.63065" height="0.40989" transform="translate(-414.27324 332.4398) rotate(-45)" fill="#cbcbcb"/><line x1="252.51682" y1="411.94892" x2="263.69236" y2="400.19802" fill="#fff"/><rect x="417.2099" y="526.59386" width="16.21654" height="0.40988" transform="translate(-416.73868 351.30978) rotate(-46.44446)" fill="#cbcbcb"/><rect x="249.33983" y="400.19802" width="30.74506" height="3.68941" fill="#fff"/><path d="M447.50342,524.81773h-31.155v-4.09934h31.155Zm-30.74506-.40993h30.33513v-3.27948H416.75836Z" transform="translate(-167.21357 -120.72534)" fill="#cbcbcb"/><path d="M812.07517,494.95319a1.0183,1.0183,0,0,0,1.01475-.947,35.99077,35.99077,0,0,1,35.79689-33.34945c1.08561,0,2.226.05519,3.38985.16409a1.018,1.018,0,0,0,.97857-.50769,42.76517,42.76517,0,0,1,78.20491,9.30121,1.01364,1.01364,0,0,0,1.00145.73468l.27075-.00821c.14085-.00472.28157-.00969.42353-.00969a30.16126,30.16126,0,0,1,29.39139,23.81379,1.01834,1.01834,0,0,0,.99648.80827h68.22433a1.01836,1.01836,0,1,0,0-2.03671H964.35872a32.21611,32.21611,0,0,0-31.16767-24.62207,44.8015,44.8015,0,0,0-81.375-9.557c-1.0017-.07807-1.98438-.11735-2.92927-.11735a38.03414,38.03414,0,0,0-37.82863,35.24346,1.01828,1.01828,0,0,0,.94464,1.08723C812.02706,494.95245,812.05117,494.95319,812.07517,494.95319Z" transform="translate(-167.21357 -120.72534)" fill="#cbcbcb"/><path d="M824.19165,508.01034H991.20223a1.01835,1.01835,0,1,0,0-2.03671H824.19165a1.01835,1.01835,0,0,0,0,2.03671Z" transform="translate(-167.21357 -120.72534)" fill="#cbcbcb"/><path d="M950.46794,522.77652h56.00965a1.01836,1.01836,0,0,0,0-2.03671H950.46794a1.01835,1.01835,0,1,0,0,2.03671Z" transform="translate(-167.21357 -120.72534)" fill="#cbcbcb"/><polygon points="522.992 647.768 519.639 647.768 518.044 634.839 522.992 634.839 522.992 647.768" fill="#9f616a"/><path d="M691.06018,771.743H680.2515v-.13671a4.21189,4.21189,0,0,1,4.20717-4.20711h6.60151Z" transform="translate(-167.21357 -120.72534)" fill="#2f2e41"/><polygon points="566.219 641.416 563.345 643.143 555.319 632.881 559.56 630.333 566.219 641.416" fill="#9f616a"/><path d="M726.57374,770.05331l-.07043-.11722a4.212,4.212,0,0,1,1.43949-5.773l5.65874-3.39979,2.23713,3.72341Z" transform="translate(-167.21357 -120.72534)" fill="#2f2e41"/><path d="M691.07674,765.82538l-.05781-.00027a45.01358,45.01358,0,0,1-4.88046-.2809c-.61767-.061-1.28768-.12724-2.09494-.19346l-.0277-.0052c-.55079-.16368-.56608-5.32925-.33257-23.01079.11962-9.05639.25513-19.32089.09379-25.85785l-.0016-.06422.04853-.04232a24.5749,24.5749,0,0,1,21.62317-5.48959l.05908.01229.0307.05193c3.29451,5.57436,8.26883,13.60381,13.07932,21.369,11.04286,17.82547,14.20119,23.06793,13.855,23.46338l-.04172.03231c-.75406.377-1.3202.66727-1.80156.91413a59.40563,59.40563,0,0,1-5.86341,2.64453l-.08852.03645-.06455-.07076c-11.20473-12.27794-20.52907-25.9931-26.29656-38.67183-.67394,4.32847-1.49195,9.98054-2.35628,15.95249-2.48545,17.17288-4.16525,28.48088-4.84147,29.16938Z" transform="translate(-167.21357 -120.72534)" fill="#2f2e41"/><path d="M694.09569,717.79784a58.69173,58.69173,0,0,1,19.62556,3.28577,44.67268,44.67268,0,0,0-10.01451-29.12533l-.04927-.05928.02523-.07276c1.76011-5.08572,3.57936-10.34247,5.205-15.57319l-.02463-.08785c-1.14009-4.05317-3.58723-8.14893-7.48106-12.52159a2.39636,2.39636,0,0,0-3.27122-.16568,30.476,30.476,0,0,0-9.82125,24.81008l.00253.0263-.00754.0255c-3.049,10.32965-4.15584,15.63527-5.13238,20.31633-.71234,3.41473-1.32914,6.37154-2.5675,10.59627A63.62216,63.62216,0,0,1,694.09569,717.79784Z" transform="translate(-167.21357 -120.72534)" fill="#cbcbcb"/><polygon points="536.719 564.419 526.8 586.013 521.133 580.277 536.719 564.419" opacity="0.1"/><circle cx="532.47798" cy="533.75944" r="7.0569" fill="#9f616a"/><path d="M691.13527,650.49848h11.20551V645.614c-2.45948-.97711-4.86621-1.80809-6.32106,0a4.88448,4.88448,0,0,0-4.88445,4.88445Z" transform="translate(-167.21357 -120.72534)" fill="#2f2e41"/><path d="M702.96116,644.75206c6.69887,0,8.57389,8.39678,8.57389,13.13382,0,2.64179-1.19474,3.58667-3.07214,3.90638l-.663-3.53611-1.55291,3.68828c-.52736.00263-1.08138-.00758-1.65574-.01824l-.52652-1.08416-1.17412,1.06471c-4.70249.007-8.50318.69249-8.50318-4.02086C694.38741,653.14884,696.03179,644.75206,702.96116,644.75206Z" transform="translate(-167.21357 -120.72534)" fill="#2f2e41"/><path d="M905.30444,318.72534c0,186-195,275-195,275s-197-89-201-275c-2.35112-109.3271,88.64762-198,198-198S905.30444,209.373,905.30444,318.72534Z" transform="translate(-167.21357 -120.72534)" fill="#6c63ff"/><polygon points="547.274 646.307 543.163 646.307 541.208 630.453 547.274 630.453 547.274 646.307" fill="#ffb7b7"/><path d="M540.22741,645.13222h7.92694a0,0,0,0,1,0,0v4.991a0,0,0,0,1,0,0h-12.918a0,0,0,0,1,0,0v0A4.991,4.991,0,0,1,540.22741,645.13222Z" fill="#2f2e41"/><polygon points="562.696 646.307 558.586 646.307 556.63 630.453 562.697 630.453 562.696 646.307" fill="#ffb7b7"/><path d="M555.64958,645.13222h7.927a0,0,0,0,1,0,0v4.991a0,0,0,0,1,0,0h-12.918a0,0,0,0,1,0,0v0A4.991,4.991,0,0,1,555.64958,645.13222Z" fill="#2f2e41"/><path d="M729.40736,719.814a3.60166,3.60166,0,0,1-.69139-5.47924l-2.70642-38.40719,7.7959.75606.21413,37.61221a3.62116,3.62116,0,0,1-4.61222,5.51816Z" transform="translate(-167.21357 -120.72534)" fill="#ffb7b7"/><path d="M714.293,759.04523l-4.52484-.21576a1.50837,1.50837,0,0,1-1.43691-1.49625l-.31578-45.78261a1.50905,1.50905,0,0,1,1.72543-1.50378l18.10216,2.62777a1.49992,1.49992,0,0,1,1.292,1.48184l2.32819,42.42243a1.50886,1.50886,0,0,1-1.50869,1.52015h-4.87812a1.50159,1.50159,0,0,1-1.49035-1.27427s-3.68464-31.82068-4.19057-31.802c-.5087.00949-3.53263,32.69486-3.53263,32.69486a1.51443,1.51443,0,0,1-1.49822,1.3296Q714.32906,759.04719,714.293,759.04523Z" transform="translate(-167.21357 -120.72534)" fill="#2f2e41"/><path d="M727.77429,688.83291a1.50258,1.50258,0,0,1-.62316-1.14012l-.57124-10.35168a4.15682,4.15682,0,0,1,8.1625-1.31653l2.5093,9.255a1.51039,1.51039,0,0,1-1.06132,1.85092l-7.138,1.93533A1.50293,1.50293,0,0,1,727.77429,688.83291Z" transform="translate(-167.21357 -120.72534)" fill="#3f3d56"/><circle cx="553.89292" cy="535.27109" r="8.23444" fill="#ffb7b7"/><path d="M726.93359,662.51115a2.65975,2.65975,0,0,1-4.58922-1.47757,2.70079,2.70079,0,0,1,.0034-.52157c.10335-.99022.67543-1.88923.53839-2.93477a1.539,1.539,0,0,0-.28165-.72045c-1.22414-1.63922-4.0977.73318-5.253-.75076-.7084-.90992.12432-2.34253-.4193-3.35952-.71748-1.34226-2.84264-.68012-4.17534-1.4152-1.48279-.81788-1.3941-3.0929-.418-4.47669a7.04185,7.04185,0,0,1,5.33854-2.71786,14.3202,14.3202,0,0,1,6.03218,1.17727,12.05493,12.05493,0,0,1,5.69972,3.95194,9.64786,9.64786,0,0,1,.97534,8.21456C729.88685,659.14346,728.188,661.17489,726.93359,662.51115Z" transform="translate(-167.21357 -120.72534)" fill="#2f2e41"/><path d="M729.32568,669.73127q-.21461-.15422-.43757-.30285a10.84928,10.84928,0,0,0-2.54657-1.24859v-2.16367h-6.37v2.00746a11.17082,11.17082,0,0,0-8.101,9.2822L707.533,709.50825a1.50116,1.50116,0,0,0,.312,1.14036,1.48163,1.48163,0,0,0,1.02381.56085,22.7729,22.7729,0,0,1,8.60949,2.94895,12.78206,12.78206,0,0,0,6.27131,1.60069,15.11672,15.11672,0,0,0,5.10476-.90888,1.49748,1.49748,0,0,0,.97829-1.3427c.15618-3.4656,1.06964-21.304,3.7295-32.18507A11.12326,11.12326,0,0,0,729.32568,669.73127Z" transform="translate(-167.21357 -120.72534)" fill="#3f3d56"/><polygon points="609.091 484 477.091 484 461.091 420 625.091 420 609.091 484" fill="#3f3d56"/><rect x="481.59087" y="593.4507" width="123" height="64.09859" fill="#fff"/><path d="M772.80444,779.27466h-125V713.176h125Zm-123-2h121V715.176h-121Z" transform="translate(-167.21357 -120.72534)" fill="#3f3d56"/><polygon points="568.304 491.547 517.878 491.547 512.091 482.453 574.091 482.453 568.304 491.547" fill="#3f3d56"/><rect x="478.09087" y="484" width="2" height="98" fill="#3f3d56"/><rect x="606.09087" y="484" width="2" height="98" fill="#3f3d56"/><path d="M730.3518,539.64136l-1.22119-1.584C786.90893,493.50122,855.7832,417.16919,855.7832,306.554A196.7304,196.7304,0,0,0,735.46411,125.03491l.77929-1.84179A198.72645,198.72645,0,0,1,857.7832,306.554C857.7832,417.98169,788.48535,494.81079,730.3518,539.64136Z" transform="translate(-167.21357 -120.72534)" fill="#3f3d56"/><path d="M666.35107,539.64185l-1.22119-1.584C722.90844,493.50171,791.7832,417.16968,791.7832,306.554a196.96192,196.96192,0,0,0-118.949-180.9336l.793-1.83594A198.96056,198.96056,0,0,1,793.7832,306.554C793.7832,417.98218,724.4851,494.81177,666.35107,539.64185Z" transform="translate(-167.21357 -120.72534)" fill="#3f3d56"/><path d="M748.25707,539.64136C690.12353,494.81128,620.82568,417.98218,620.82568,306.554a198.95968,198.95968,0,0,1,120.15771-182.77l.793,1.83593A196.9616,196.9616,0,0,0,622.82568,306.554c0,110.61523,68.87427,186.94726,126.65259,231.50341Z" transform="translate(-167.21357 -120.72534)" fill="#3f3d56"/><path d="M684.25732,539.64136C626.12353,494.81128,556.82568,417.98267,556.82568,306.554a198.5883,198.5883,0,0,1,122.3518-183.70215l.771,1.8457A196.59273,196.59273,0,0,0,558.82568,306.554c0,110.61621,68.87451,186.94775,126.65283,231.50341Z" transform="translate(-167.21357 -120.72534)" fill="#3f3d56"/><rect x="539.09087" width="2" height="420" fill="#3f3d56"/><line x1="481.59087" y1="616.29921" x2="518.26494" y2="579.62514" fill="#fff"/><rect x="641.2089" y="717.68761" width="51.86514" height="1.99979" transform="translate(-480.00116 561.51361) rotate(-45)" fill="#3f3d56"/><line x1="481.59087" y1="636.33071" x2="536.11449" y2="579" fill="#fff"/><rect x="636.50738" y="727.39076" width="79.1178" height="1.99973" transform="translate(-484.86538 595.71211) rotate(-46.44446)" fill="#3f3d56"/><rect x="466.09087" y="579" width="150" height="18" fill="#fff"/><path d="M784.30444,718.72534h-152v-20h152Zm-150-2h148v-16h-148Z" transform="translate(-167.21357 -120.72534)" fill="#3f3d56"/><path d="M681.81868,708.09726a2.74948,2.74948,0,0,0,2.47585-3.41248l8.17284-5.35471-4.661-2.01364-7.04648,5.39363a2.7644,2.7644,0,0,0,1.05884,5.3872Z" transform="translate(-167.21357 -120.72534)" fill="#9f616a"/><path d="M689.68278,704.23969l-.10307-.0729c-1.94508-1.4316-4.10444-2.91207-6.19271-4.34382l-.12543-.086.0988-.11562c4.45717-5.21483,8.91914-10.66183,13.235-15.93112l-.00828-.019.04066-.0207.01722-.021.00921.00761.026-.01321-.11762.00907-1.3302-10.43178a6.6868,6.6868,0,0,1,.14305-7.165,5.67453,5.67453,0,0,1,7.38427-1.68608,4.84545,4.84545,0,0,1,2.12772,3.61847,4.37007,4.37007,0,0,1-1.15839,3.57842,150.47526,150.47526,0,0,1,.60806,15.22847l.00021.04766-.02951.03925c-4.601,5.84525-9.65484,11.6594-14.5423,17.28209Z" transform="translate(-167.21357 -120.72534)" fill="#cbcbcb"/><path d="M691.2667,672.1745a3.52839,3.52839,0,0,1,.08022.54988L705.749,681.0329l3.50063-2.01525,3.73189,4.88556-7.48885,5.3376-16.43062-12.96222a3.51887,3.51887,0,1,1,2.20461-4.10409Z" transform="translate(-167.21357 -120.72534)" fill="#ffb7b7"/><path d="M706.26342,680.32639a1.50256,1.50256,0,0,1,.43355-1.22484l7.33-7.33173a4.15681,4.15681,0,0,1,6.42637,5.202l-5.22,8.04373a1.51039,1.51039,0,0,1-2.08685.44426l-6.20383-4.026A1.503,1.503,0,0,1,706.26342,680.32639Z" transform="translate(-167.21357 -120.72534)" fill="#3f3d56"/><path d="M606.53371,608.95319a1.01829,1.01829,0,0,1-1.01475-.947,35.99077,35.99077,0,0,0-35.7969-33.34945c-1.08561,0-2.226.05519-3.38984.16409a1.018,1.018,0,0,1-.97858-.50769,42.76517,42.76517,0,0,0-78.20491,9.30121,1.01364,1.01364,0,0,1-1.00145.73468l-.27075-.00821c-.14084-.00472-.28156-.00969-.42352-.00969a30.16126,30.16126,0,0,0-29.39139,23.81379,1.01836,1.01836,0,0,1-.99648.80827H386.8408a1.01836,1.01836,0,1,1,0-2.03671h67.40935a32.21612,32.21612,0,0,1,31.16768-24.62207,44.80149,44.80149,0,0,1,81.375-9.557c1.0017-.07807,1.98438-.11735,2.92927-.11735a38.03415,38.03415,0,0,1,37.82864,35.24346,1.01828,1.01828,0,0,1-.94464,1.08723C606.58182,608.95245,606.5577,608.95319,606.53371,608.95319Z" transform="translate(-167.21357 -120.72534)" fill="#cbcbcb"/><path d="M594.41722,622.01034H427.40665a1.01836,1.01836,0,1,1,0-2.03671H594.41722a1.01835,1.01835,0,1,1,0,2.03671Z" transform="translate(-167.21357 -120.72534)" fill="#cbcbcb"/><path d="M468.14093,636.77652H412.13129a1.01836,1.01836,0,1,1,0-2.03671h56.00964a1.01836,1.01836,0,1,1,0,2.03671Z" transform="translate(-167.21357 -120.72534)" fill="#cbcbcb"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" data-name="Layer 1" width="888" height="677.20705" viewBox="0 0 888 677.20705" xmlns:xlink="http://www.w3.org/1999/xlink"><polygon points="307.693 659.535 316.417 662.148 330.648 629.74 317.772 625.884 307.693 659.535" fill="#ffb8b8"/><path d="M462.32048,767.41626l17.18156,5.1462.00069.00021a11.43063,11.43063,0,0,1,7.66935,14.22912l-.10659.35581-28.13113-8.4259Z" transform="translate(-156 -111.39648)" fill="#2f2e41"/><polygon points="390.602 666.663 399.153 663.528 391.13 629.055 378.51 633.682 390.602 666.663" fill="#ffb8b8"/><path d="M543.39769,776.06729l16.83948-6.17423.00068-.00025a11.43063,11.43063,0,0,1,14.6658,6.79715l.12785.34873L547.46038,787.1476Z" transform="translate(-156 -111.39648)" fill="#2f2e41"/><polygon points="322.321 600.868 307.464 649.897 325.292 655.097 342.378 609.782 322.321 600.868" fill="#2f2e41"/><polygon points="369.121 612.011 382.493 658.068 401.064 649.154 387.693 606.068 369.121 612.011" fill="#2f2e41"/><path d="M518.16217,731.71429a203.9796,203.9796,0,0,1-35.45752-3.27034l-.298-.06021v-11.3301l-6.78986-.75447,5.29979-18.92774c-2.47016-29.16024,1.03993-66.08934,2.17563-76.71865.26025-2.49265.43146-3.89132.43146-3.89132l5.95867-50.64955,9.97024-9.20308,4.52936,2.94751,8.40181,8.40145c9.766,24.0342,17.51548,46.661,17.56426,48.11483l32.75141,104.95321-.2209.156C551.10618,729.50964,533.77044,731.71429,518.16217,731.71429Z" transform="translate(-156 -111.39648)" fill="#3f3d56"/><polygon points="337.708 477.181 335.418 491.475 350.921 497.87 337.708 477.181" opacity="0.2"/><path d="M524.12819,556.834H488.28426a2.78413,2.78413,0,0,1-2.781-2.781v-15.45a20.703,20.703,0,0,1,41.40592,0v15.45A2.78412,2.78412,0,0,1,524.12819,556.834Z" transform="translate(-156 -111.39648)" fill="#2f2e41"/><circle cx="354.16363" cy="429.32816" r="15.17868" fill="#ffb8b8"/><path d="M531.807,540.148H509.88932l-.22481-3.14673-1.12374,3.14673h-3.37485l-.4454-6.23672-2.22727,6.23672h-6.53018v-.309a16.39537,16.39537,0,0,1,16.37682-16.377H515.43a16.39549,16.39549,0,0,1,16.377,16.377Z" transform="translate(-156 -111.39648)" fill="#2f2e41"/><path d="M509.70994,559.718a2.84058,2.84058,0,0,1-.49216-.04345l-16.04911-2.83169V530.31841h17.667l-.4374.51c-6.08554,7.09733-1.50079,18.60574,1.77373,24.834a2.73982,2.73982,0,0,1-.21772,2.90894A2.76982,2.76982,0,0,1,509.70994,559.718Z" transform="translate(-156 -111.39648)" fill="#2f2e41"/><path d="M538.02851,623.82967h-8.79718a1.1313,1.1313,0,0,1-1.13-1.02492l-1.7609-18.04893h14.579l-1.7609,18.04871A1.13131,1.13131,0,0,1,538.02851,623.82967Z" transform="translate(-156 -111.39648)" fill="#1976d2"/><path d="M540.89615,607.02652H526.36369a1.13668,1.13668,0,0,1-1.13534-1.13535v-2.72484a1.13668,1.13668,0,0,1,1.13534-1.13534h14.53246a1.13668,1.13668,0,0,1,1.13535,1.13534v2.72484A1.13669,1.13669,0,0,1,540.89615,607.02652Z" transform="translate(-156 -111.39648)" fill="#2f2e41"/><path d="M483.8924,616.06358l0,0a27.88129,27.88129,0,0,0,33.46807,6.76478l3.30359-1.635Z" transform="translate(-156 -111.39648)" opacity="0.2"/><path d="M536.71057,612.5695a6.96579,6.96579,0,0,0-10.67635.32242l-15.32558-4.30242-4.88606,8.67563,21.728,5.77187a7.00355,7.00355,0,0,0,9.16-10.4675Z" transform="translate(-156 -111.39648)" fill="#ffb8b8"/><path d="M500.13852,622.19727c-7.28644.00072-17.14532-4.27146-28.84884-12.546a5.73089,5.73089,0,0,1-2.4134-3.92759c-.86328-5.46918,4.4715-12.863,4.99455-13.57174l5.60811-15.40061c.06456-.25028,1.87184-6.91355,6.409-9.28432a7.43841,7.43841,0,0,1,6.21386-.26479c8.6423,3.147,1.894,27.44817.96757,30.63508l11.45016,5.38938,7.27119,4.6349,9.95773,1.04175-2.70358,12.51186-15.12275.34024A15.43364,15.43364,0,0,1,500.13852,622.19727Z" transform="translate(-156 -111.39648)" fill="#3f3d56"/><path d="M1043.67468,508.05561A98.57951,98.57951,0,1,0,854.454,546.865c-.09637-.10718-.196-.21149-.29193-.31909a98.66626,98.66626,0,0,0,17.95428,27.78283c.0224.02466.04541.04871.06788.07337.60559.66,1.21466,1.31659,1.83782,1.9599a98.28006,98.28006,0,0,0,69.52936,30.25354l-3.33105,180.9292h10.29126l-2.08283-119.41541,14.88678-7.83758-2.27093-4.31354-12.711,6.692-.97791-56.06378A98.57812,98.57812,0,0,0,1043.67468,508.05561Z" transform="translate(-156 -111.39648)" fill="#e6e6e6"/><path d="M751.0871,460.00819a115.52648,115.52648,0,1,0-221.74982,45.4812c-.113-.12561-.22968-.24784-.3421-.37393a115.62807,115.62807,0,0,0,21.04083,32.559c.02625.02893.05322.05706.07959.086.70972.7735,1.42346,1.543,2.15375,2.29682a115.17561,115.17561,0,0,0,81.48224,35.45447l-3.90375,212.033h12.06042l-2.44091-139.94428,17.446-9.185-2.66138-5.05505-14.89618,7.84247-1.146-65.70178A115.52494,115.52494,0,0,0,751.0871,460.00819Z" transform="translate(-156 -111.39648)" fill="#e6e6e6"/><path d="M419.25891,414.8147A131.46683,131.46683,0,1,0,166.912,466.57142c-.12855-.14295-.26138-.282-.38931-.42551a131.58166,131.58166,0,0,0,23.944,37.05149c.02989.03289.06059.065.09055.09787.80763.88021,1.61988,1.75582,2.45091,2.61373a131.0676,131.0676,0,0,0,92.72516,40.3465L281.291,787.54475h13.72454L292.23788,628.291,312.091,617.83869l-3.02856-5.75256-16.95154,8.92456-1.30411-74.76727A131.465,131.465,0,0,0,419.25891,414.8147Z" transform="translate(-156 -111.39648)" fill="#e6e6e6"/><circle cx="756.68549" cy="85.97574" r="85.97575" fill="#ff6584"/><circle cx="245.55884" cy="187.61628" r="172.3117" fill="#1976d2"/><path d="M274.32944,183.92207A172.32513,172.32513,0,0,0,561.45166,366.29173,172.32654,172.32654,0,1,1,274.32944,183.92207Z" transform="translate(-156 -111.39648)" opacity="0.2" style="isolation:isolate"/><polygon points="246.032 187.616 246.506 187.616 255.027 676.148 237.038 676.148 246.032 187.616" fill="#3f3d56"/><rect x="401.08538" y="564.58106" width="32.19013" height="8.5209" transform="translate(-372.96557 148.44822) rotate(-27.76587)" fill="#3f3d56"/><path d="M665.11523,782.975s.62171-13.02673,13.3664-11.51257" transform="translate(-156 -111.39648)" fill="#3f3d56"/><circle cx="505.51413" cy="652.80328" r="6.37865" fill="#1976d2"/><rect x="504.47623" y="663.54456" width="1.80054" height="12.60376" fill="#3f3d56"/><path d="M223.08289,781.17445s.62168-13.02674,13.36641-11.51258" transform="translate(-156 -111.39648)" fill="#3f3d56"/><circle cx="63.48181" cy="651.00275" r="6.37865" fill="#1976d2"/><rect x="62.44391" y="661.74402" width="1.80054" height="12.60376" fill="#3f3d56"/><path d="M327.51416,782.07471s.6217-13.02673,13.36642-11.51257" transform="translate(-156 -111.39648)" fill="#3f3d56"/><circle cx="167.91309" cy="651.90302" r="6.37865" fill="#1976d2"/><rect x="166.87518" y="662.64429" width="1.80054" height="12.60376" fill="#3f3d56"/><path d="M605.24329,194.695l12.79486-10.23341c-9.93976-1.09662-14.02381,4.3243-15.69525,8.615-7.76532-3.22446-16.21882,1.00135-16.21882,1.00135l25.6001,9.29375A19.37211,19.37211,0,0,0,605.24329,194.695Z" transform="translate(-156 -111.39648)" fill="#3f3d56"/><path d="M799.827,298.93629l12.7948-10.23343c-9.93976-1.09662-14.02381,4.32431-15.69526,8.615-7.76532-3.22446-16.21881,1.00134-16.21881,1.00134l25.6001,9.29376A19.37241,19.37241,0,0,0,799.827,298.93629Z" transform="translate(-156 -111.39648)" fill="#3f3d56"/><path d="M589.95459,387.88883l12.7948-10.23343c-9.93976-1.09662-14.0238,4.32431-15.69525,8.615-7.76532-3.22445-16.21881,1.00138-16.21881,1.00138l25.60009,9.29373A19.37244,19.37244,0,0,0,589.95459,387.88883Z" transform="translate(-156 -111.39648)" fill="#3f3d56"/><path d="M839.65527,787.70325s.62171-13.02673,13.3664-11.51257" transform="translate(-156 -111.39648)" fill="#3f3d56"/><path d="M719.91943,787.70325s.62171-13.02673,13.3664-11.51257" transform="translate(-156 -111.39648)" fill="#3f3d56"/><path d="M283.2887,787.70325s.6217-13.02673,13.36642-11.51257" transform="translate(-156 -111.39648)" fill="#3f3d56"/><path d="M893.67145,787.70325s.6217-13.02673,13.36639-11.51257" transform="translate(-156 -111.39648)" fill="#3f3d56"/><path d="M868.46387,787.70325s.6217-13.02673,13.36639-11.51257" transform="translate(-156 -111.39648)" fill="#3f3d56"/><path d="M816.465,787.70325s-.62171-13.02673-13.3664-11.51257" transform="translate(-156 -111.39648)" fill="#3f3d56"/><path d="M609.40308,787.70325s-.62171-13.02673-13.3664-11.51257" transform="translate(-156 -111.39648)" fill="#3f3d56"/><path d="M437.4516,787.70325s-.6217-13.02673-13.36643-11.51257" transform="translate(-156 -111.39648)" fill="#3f3d56"/><path d="M254.69688,787.70325s-.6217-13.02673-13.36641-11.51257" transform="translate(-156 -111.39648)" fill="#3f3d56"/><path d="M947.90442,787.70325s-.62171-13.02673-13.3664-11.51257" transform="translate(-156 -111.39648)" fill="#3f3d56"/><path d="M870.4812,788.60352s-.6217-13.02673-13.36639-11.51257" transform="translate(-156 -111.39648)" fill="#3f3d56"/><rect y="674.60352" width="888" height="2" fill="#3f3d56"/></svg>
\ No newline at end of file
......@@ -5,6 +5,12 @@ q-layout.fileman(view='hHh lpR lFr', container)
q-icon(name='img:/_assets/icons/fluent-folder.svg', left, size='md')
span {{t(`fileman.title`)}}
q-toolbar(dark)
q-btn.q-mr-sm.acrylic-btn(
flat
color='white'
label='EN'
style='height: 40px;'
)
q-input(
dark
v-model='state.search'
......@@ -200,12 +206,11 @@ q-layout.fileman(view='hHh lpR lFr', container)
:bar-style='barStyle'
style='height: 100%;'
)
.fileman-emptylist(v-if='files.length < 1')
template(v-if='state.fileListLoading')
q-spinner.q-mr-sm(color='primary', size='xs', :thickness='3')
span.text-primary Loading...
template(v-else)
q-icon.q-mr-sm(name='las la-folder-open', size='sm')
.fileman-loadinglist(v-if='state.fileListLoading')
q-spinner.q-mr-sm(color='primary', size='64px', :thickness='1')
span.text-primary Fetching folder contents...
.fileman-emptylist(v-else-if='files.length < 1')
img(src='/_assets/icons/carbon-copy-empty-box.svg')
span This folder is empty.
q-list.fileman-filelist(
v-else
......@@ -394,8 +399,9 @@ const files = computed(() => {
}).map(f => {
switch (f.type) {
case 'folder': {
console.info(f.children)
f.icon = fileTypes.folder.icon
f.caption = t('fileman.folderChildrenCount', f.children, { count: f.children })
f.caption = t('fileman.folderChildrenCount', { count: f.children }, f.children)
break
}
case 'page': {
......@@ -475,7 +481,7 @@ const currentFileDetails = computed(() => {
// WATCHERS
watch(() => state.currentFolderId, async (newValue) => {
await loadTree(newValue)
await loadTree({ parentId: newValue })
})
// METHODS
......@@ -485,11 +491,11 @@ function close () {
}
async function treeLazyLoad (nodeId, { done, fail }) {
await loadTree(nodeId, ['folder'])
await loadTree({ parentId: nodeId, types: ['folder'] })
done()
}
async function loadTree (parentId, types) {
async function loadTree ({ parentId = null, parentPath = null, types, initLoad = false }) {
if (state.isFetching) { return }
state.isFetching = true
if (!parentId) {
......@@ -579,7 +585,7 @@ async function loadTree (parentId, types) {
type: 'folder',
title: item.title,
fileName: item.fileName,
children: 0
children: item.childrenCount || 0
})
}
break
......@@ -664,7 +670,7 @@ function newFolder (parentId) {
parentId
}
}).onOk(() => {
loadTree(parentId)
loadTree({ parentId })
})
}
......@@ -676,7 +682,7 @@ function renameFolder (folderId) {
}
}).onOk(() => {
treeComp.value.resetLoaded()
loadTree(folderId)
loadTree({ parentId: folderId })
})
}
......@@ -698,13 +704,13 @@ function delFolder (folderId, mustReload = false) {
state.treeRoots = state.treeRoots.filter(n => n !== folderId)
}
if (mustReload) {
loadTree(state.currentFolderId, null)
loadTree({ parentId: state.currentFolderId })
}
})
}
function reloadFolder (folderId) {
loadTree(folderId, null)
loadTree({ parentId: folderId })
treeComp.value.resetLoaded()
}
......@@ -899,7 +905,7 @@ function delItem (item) {
// MOUNTED
onMounted(() => {
loadTree()
loadTree({})
})
</script>
......@@ -956,17 +962,43 @@ onMounted(() => {
height: 100%;
}
&-loadinglist {
padding: 16px;
font-style: italic;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
> span {
margin-top: 16px;
}
}
&-emptylist {
padding: 16px;
font-style: italic;
font-size: 1.5em;
font-weight: 300;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
> img {
opacity: .25;
width: 200px;
}
@at-root .body--light & {
color: $grey-6;
}
@at-root .body--dark & {
color: $dark-4;
color: $grey-7;
> img {
filter: invert(1);
}
}
}
......
......@@ -143,12 +143,14 @@ async function create () {
mutation: gql`
mutation createFolder (
$siteId: UUID!
$locale: String!
$parentId: UUID
$pathName: String!
$title: String!
) {
createFolder (
siteId: $siteId
locale: $locale
parentId: $parentId
pathName: $pathName
title: $title
......@@ -162,6 +164,7 @@ async function create () {
`,
variables: {
siteId: siteStore.id,
locale: 'en',
parentId: props.parentId,
pathName: state.path,
title: state.title
......
......@@ -81,7 +81,7 @@ q-dialog(ref='dialogRef', @hide='onDialogHide')
:color='state.displayMode === `path` ? `positive` : `grey`'
size='xs'
)
q-item-section.q-pr-sm Browse Using Paths
q-item-section.q-pr-sm {{ t('pageSaveDialog.displayModePath') }}
q-item(clickable, @click='state.displayMode = `title`')
q-item-section(side)
q-icon(
......@@ -89,7 +89,7 @@ q-dialog(ref='dialogRef', @hide='onDialogHide')
:color='state.displayMode === `title` ? `positive` : `grey`'
size='xs'
)
q-item-section.q-pr-sm Browse Using Titles
q-item-section.q-pr-sm {{ t('pageSaveDialog.displayModeTitle') }}
q-space
q-btn.acrylic-btn(
icon='las la-times'
......@@ -131,18 +131,24 @@ const props = defineProps({
mode: {
type: String,
required: false,
default: 'save'
default: 'pageSave'
},
pageId: {
itemId: {
type: String,
required: true
required: false,
default: ''
},
folderPath: {
type: String,
required: false,
default: ''
},
pageName: {
itemTitle: {
type: String,
required: false,
default: ''
},
pagePath: {
itemFileName: {
type: String,
required: false,
default: ''
......@@ -177,33 +183,12 @@ const state = reactive({
currentFileId: '',
treeNodes: {},
treeRoots: [],
fileList: [
{
id: '1',
type: 'folder',
title: 'Beep Boop'
},
{
id: '2',
type: 'folder',
title: 'Second Folder'
},
{
id: '3',
type: 'page',
title: 'Some Page',
pageType: 'markdown'
}
],
fileList: [],
title: '',
path: ''
path: '',
typesToFetch: []
})
const displayModes = [
{ value: 'title', label: t('pageSaveDialog.displayModeTitle') },
{ value: 'path', label: t('pageSaveDialog.displayModePath') }
]
const thumbStyle = {
right: '1px',
borderRadius: '5px',
......@@ -249,23 +234,32 @@ async function save () {
}
async function treeLazyLoad (nodeId, { done, fail }) {
await loadTree(nodeId, ['folder', 'page'])
await loadTree({
parentId: nodeId,
types: ['folder', 'page']
})
done()
}
async function loadTree (parentId, types) {
async function loadTree ({ parentId = null, parentPath = null, types, initLoad = false }) {
try {
const resp = await APOLLO_CLIENT.query({
query: gql`
query loadTree (
$siteId: UUID!
$parentId: UUID
$parentPath: String
$types: [TreeItemType]
$includeAncestors: Boolean
$includeRootItems: Boolean
) {
tree (
siteId: $siteId
parentId: $parentId
parentPath: $parentPath
types: $types
includeAncestors: $includeAncestors
includeRootItems: $includeRootItems
) {
__typename
... on TreeItemFolder {
......@@ -290,7 +284,10 @@ async function loadTree (parentId, types) {
variables: {
siteId: siteStore.id,
parentId,
types
parentPath,
types,
includeAncestors: initLoad,
includeRootItems: initLoad
},
fetchPolicy: 'network-only'
})
......@@ -344,16 +341,26 @@ function newFolder (parentId) {
parentId
}
}).onOk(() => {
loadTree(parentId)
loadTree({ parentId })
})
}
// MOUNTED
onMounted(() => {
loadTree()
state.title = props.pageName || ''
state.path = props.pagePath || ''
switch (props.mode) {
case 'pageSave': {
state.typesToFetch = ['folder', 'page']
break
}
}
loadTree({
parentPath: props.folderPath,
types: state.typesToFetch,
initLoad: true
})
state.title = props.itemTitle || ''
state.path = props.itemFileName || ''
})
</script>
......
......@@ -1600,8 +1600,8 @@
"common.actions.newFolder": "New Folder",
"common.actions.duplicate": "Duplicate",
"common.actions.moveTo": "Move To",
"pageSaveDialog.displayModeTitle": "Title",
"pageSaveDialog.displayModePath": "Path",
"pageSaveDialog.displayModeTitle": "Browse Using Titles",
"pageSaveDialog.displayModePath": "Browse Using Paths",
"folderDeleteDialog.title": "Confirm Delete Folder",
"folderDeleteDialog.confirm": "Are you sure you want to delete folder {name} and all its content?",
"folderDeleteDialog.folderId": "Folder ID {id}",
......
......@@ -2,7 +2,7 @@
q-page.admin-terminal
.row.q-pa-md.items-center
.col-auto
img.admin-icon.animated.fadeInLeft(src='/_assets/icons/fluent-bot.svg')
img.admin-icon.animated.fadeInLeft(src='/_assets/icons/fluent-bot-animated.svg')
.col.q-pl-md
.text-h5.text-primary.animated.fadeInLeft {{ t('admin.scheduler.title') }}
.text-subtitle1.text-grey.animated.fadeInLeft.wait-p2s {{ t('admin.scheduler.subtitle') }}
......
......@@ -466,12 +466,13 @@ function togglePageData () {
function duplicatePage () {
$q.dialog({
component: defineAsyncComponent(() => import('../components/PageSaveDialog.vue')),
component: defineAsyncComponent(() => import('../components/TreeBrowserDialog.vue')),
componentProps: {
mode: 'duplicate',
pageId: pageStore.id,
pageName: pageStore.title,
pagePath: pageStore.path
mode: 'duplicatePage',
folderPath: '',
itemId: pageStore.id,
itemTitle: pageStore.title,
itemFileName: pageStore.path
}
}).onOk(() => {
// TODO: change route to new location
......@@ -480,12 +481,13 @@ function duplicatePage () {
function renamePage () {
$q.dialog({
component: defineAsyncComponent(() => import('../components/PageSaveDialog.vue')),
component: defineAsyncComponent(() => import('../components/TreeBrowserDialog.vue')),
componentProps: {
mode: 'rename',
pageId: pageStore.id,
pageName: pageStore.title,
pagePath: pageStore.path
mode: 'renamePage',
folderPath: '',
itemId: pageStore.id,
itemTitle: pageStore.title,
itemFileName: pageStore.path
}
}).onOk(() => {
// TODO: change route to new location
......
import { defineStore } from 'pinia'
import gql from 'graphql-tag'
import { cloneDeep, last, pick, transform } from 'lodash-es'
import { cloneDeep, initial, last, pick, transform } from 'lodash-es'
import { DateTime } from 'luxon'
import { useSiteStore } from './site'
......@@ -131,6 +131,9 @@ export const usePageStore = defineStore('page', {
path: (last(result)?.path || pathPrefix) + `/${value}`
})
}, [])
},
folderPath: (state) => {
return initial(state.path.split('/')).join('/')
}
},
actions: {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment