From e9c1fcf116286f22be1dd0ea21d7b48869affd75 Mon Sep 17 00:00:00 2001 From: Cosmo Myzrail Gorynych Date: Wed, 22 Dec 2021 18:38:59 +1200 Subject: [PATCH] :sparkles: Revano of the asset viewer: it now supports grouping your assets with categories, as well as displays handy information in forms of small icons. Besides that, every tab now supports three display modes: list/table view, regular cards, and large grid of cards. --- app/data/i18n/English.json | 22 +- app/data/img/music.png | Bin 2447 -> 0 bytes app/data/img/particles.png | Bin 3394 -> 0 bytes app/data/img/wave.png | Bin 2773 -> 0 bytes src/icons/circle.svg | 1 + src/icons/music.svg | 1 + src/icons/polyline.svg | 3 + src/icons/volume-2.svg | 1 + src/js/projectMigrationScripts/1.8.0.js | 9 + src/node_requires/resources/IAsset.d.ts | 5 + src/node_requires/resources/commonTypes.d.ts | 5 + src/node_requires/resources/fonts/index.js | 3 +- src/node_requires/resources/projects/index.ts | 2 +- src/node_requires/resources/rooms/IRoom.d.ts | 54 ++++ .../resources/rooms/defaultRoom.ts | 30 +++ src/node_requires/resources/rooms/index.ts | 55 +++++ src/node_requires/resources/skeletons.js | 2 +- src/node_requires/resources/textures/index.js | 5 +- src/riotTags/dnd-processor.tag | 4 +- src/riotTags/fonts-panel.tag | 8 +- src/riotTags/main-menu/icon-panel.tag | 10 +- src/riotTags/particles/fx-panel.tag | 10 +- src/riotTags/project-selector.tag | 14 +- .../tabs/rendering-settings.tag | 5 +- src/riotTags/rooms/rooms-panel.tag | 169 ++++--------- src/riotTags/shared/asset-viewer.tag | 231 ++++++++++++++++-- src/riotTags/shared/group-editor.tag | 58 +++++ src/riotTags/shared/icon-selector.tag | 10 +- src/riotTags/sounds/sound-editor.tag | 2 +- src/riotTags/sounds/sound-recorder.tag | 5 +- src/riotTags/sounds/sounds-panel.tag | 9 +- src/riotTags/style-editor.tag | 17 ++ src/riotTags/styles-panel.tag | 4 + src/riotTags/textures/textures-panel.tag | 13 +- src/riotTags/types-panel.tag | 21 ++ src/styl/buildingBlocks.styl | 162 ++++++------ src/styl/common.styl | 12 +- src/styl/inputs.styl | 2 +- src/styl/tags/rooms/rooms-panel.styl | 15 +- src/styl/tags/shared/asset-viewer.styl | 7 +- 40 files changed, 709 insertions(+), 277 deletions(-) delete mode 100644 app/data/img/music.png delete mode 100644 app/data/img/particles.png delete mode 100644 app/data/img/wave.png create mode 100644 src/icons/circle.svg create mode 100644 src/icons/music.svg create mode 100644 src/icons/polyline.svg create mode 100644 src/icons/volume-2.svg create mode 100644 src/node_requires/resources/IAsset.d.ts create mode 100644 src/node_requires/resources/commonTypes.d.ts create mode 100644 src/node_requires/resources/rooms/IRoom.d.ts create mode 100644 src/node_requires/resources/rooms/defaultRoom.ts create mode 100644 src/node_requires/resources/rooms/index.ts create mode 100644 src/riotTags/shared/group-editor.tag diff --git a/app/data/i18n/English.json b/app/data/i18n/English.json index ae7779209..3b5fc9917 100644 --- a/app/data/i18n/English.json +++ b/app/data/i18n/English.json @@ -24,6 +24,7 @@ "done": "Done!", "duplicate": "Duplicate", "exit": "Exit", + "edit": "Edit", "fastimport": "Fast Import", "filter": "Filter:", "loading": "Loading…", @@ -34,6 +35,7 @@ "none": "None", "norooms": "You need at least one room to compile your app.", "notfoundorunknown": "Unknown file. Make sure the file really exists", + "nothingToShowFiller": "There is nothing to show here!", "ok": "Ok", "open": "Open", "openproject": "Open project…", @@ -84,12 +86,11 @@ "value": "Value", "delete": "Delete" }, - "copyCustomProperties": { - "customProperties": "Custom Properties", - "addProperty": "Add Property", - "property": "Property", - "value": "Value", - "delete": "Delete" + "assetViewer": { + "addNewGroup": "New Group", + "ungrouped": "Show ungrouped", + "newGroupName": "New group", + "groupDeletionConfirmation": "Are you sure you want to delete this group? All its assets will be ungrouped." }, "colorPicker": { "current": "New", @@ -139,6 +140,11 @@ "noEntries": "No entries yet.", "addRow": "Add a row" }, + "groupEditor": { + "groupEditor": "Group Editor", + "icon": "Icon:", + "color": "Color:" + }, "intro": { "loading": "Please wait: kittens are gathering speed of light!", "newProject": { @@ -447,7 +453,9 @@ "testtext": "Test text 0123 +", "textWrap": "Word wrap", "textWrapWidth": "Max width:", - "useCustomFont": "Use custom font…" + "useCustomFont": "Use custom font…", + "code": "Code", + "copyCode": "Copy" }, "fonts": { "fonts": "Fonts", diff --git a/app/data/img/music.png b/app/data/img/music.png deleted file mode 100644 index 83233308c85d8586339cc8273f9af2863daff415..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2447 zcmah~i#yc$7yf)_7=xLN+$Pdal5wpW%dC6DBE&T05+e<{UkXXfl)kGqE6gS}rN#(J zsh1oKd1xF&DOWM(>(TEH= z91Uh6+9c6LLKeIr_KT`%oO+TK!y$I9^-^k-yH2Uc5Vz*NN7&uYw(`L%?#jDo%Da>W z4$Il5G8k&pZYIFPDPcLI!$C3v;OdQ3n6_X6l%N4Ma$%)n`?-ycWLzB-ERLqgMl~gf zn(C<37HMdw0q@1tQO3)WJU5xhgoKfAYo|8kL2Okj<;h`OXOk7S_9KJ{1J<2`a)~P) z{a4BwPx}xm5Y(O@8VZLB4sjsj09QZKiPTjS&dn;1yWsJa#W0)Dt}q`K&BX zNMZjD6Gm1ycwKNY4v%D#@ZmnqE408kwKXJUx)|SajKP>%wX#zjKlYmd4<3Jei$z#C zQgj>AZk(JxRZ`}&;dytdi@)FT_XO~NyELC zxmt@ijvrPAeo*j&>ObAbRYANie>bGs-NonY`*0sZM&{-h@JBY-(26s%5$RY4Y`<2W z)D{yXR@XmpGZ4d2X~euaaJBal8bZ zo#gwT?tcEsfq}i$*gjzc`nWuLaj5Q)5p6wzc_M@lkpNtn zr-WrC<22led23#{orSe*E(9T&Xy8dQ@-&xSz)hfWch}YI!f#3p(BwqJvLx`OT-|4E~Y&Qe_eOuBE5qTbEj@om3QZrt%lx6vPgHulBJ zlVr(#6E5NCIMN9PVGADIO%d*oG`Eqr9Oj%!tnVU&IK!he^Nl*VPAS=lz>r(%yENjq zqX?;rV-Lcwn@e~;Y9&+MIb3=+q-i;^&#)EPl|;qIeo~f&n|z!6~}(AcxY1WjS*b?=*pT5bgOE8LSVr<^JLI%!J8qVD|!~f$(<*=By-G~M?K(=I)uv^PLDSu@FH8jPXhuE zf5NjAkR*T+$PlBeiuY{FQC`<@OjZ@r-}GHF93WlinCt*3W;=bH6X8g*akjuk?NA zP&rsXpjZrOl=17tse_eqGH{W30tG}`Qp%z9V`fFs%l4Z*lt5vy9z~6P#U@oeIH!y> zNnB}Jn54)NX^GtHD{(Q#F<@BDe_j!;UXKMc0rfGg16nGzPDvYR>Ky+$m@)D3dadU` z;eFLx^rs6sjH3SbSFX?T;D&$I;2Rex9d$ItbU9qx9|IeFFzq_^F8=g!#XAG(FKqd= z5-}kpxJashbwztMrUPmuc5m`9h+~P}CxE&~iKeY*`2nL^t~m?CHBfq&R@r+&iW2w& zKdA@imPVK}UesF79O5qHK#0&Q=CeKgxF~Dz?tj@P;6J=&mJ-q8m zbsE_CGo{K6)ZbKAmn6rRV~cNU$vg=y-R)G%SY!r8TkH6s@avzI3J|$Vg63?3#`yRH?{`Z|q16## z{~W%!+sx9l(hUW27dn@Jy?lI0lKInwexU4U382~y-Ts59d8pO_B88x32?^_{O^LJ8 z(jB}rWSbU&r%+yt5db~+!`&9?0}F=7ol_W?opEkoBUlMP#%Uk}Wrw+kXyn_fGlpx; zAXu|?Obr9S?CTv@LFVMD-%cg{zaouSyx`HDf44EXEzu%c$98%dzd|{_$WfLzMu7V>=ehb+@%*B5mshcDe<|5_D+ zh&R+rE8=(9f@Jle#pIm+Z7M3X;AmRXxXrV(_7c9-C~K#>N4CJuv`id)|2N$V$)fZ9 z@B*w2GSR^1kS)L|v?91uUt-?hZ9*OyTe=VIz5KMVLbYTd?*pvq$+(?;Q!7JU4!D8Bn7ESaCU1u!Ta7{aWv|!mhKrfaBeVk*!-5FrD3|` z<8@9uLc{b*Y>o@#%{E2(U7y?^$6gp>9pnG7p@uMYdVgVhC*q6a&jeHb{n`2xYo$!UgNJn&DrKf`dP$1ez>7RY|Ug>n3YnOn6Ys-hb9=t@yh*;N8n7 zKSkVTS4s&ojXG}*-Fx{;Ow+x_9cG@+HoF+vYgnRMR$i`3-lDS6c+{sLQKZ$q;#vtlOx3+pmJj{pDw diff --git a/app/data/img/particles.png b/app/data/img/particles.png deleted file mode 100644 index 1d356ffa9b6385e4645a5e091d09702e3db5162e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3394 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 diff --git a/src/icons/circle.svg b/src/icons/circle.svg new file mode 100644 index 000000000..b0090882b --- /dev/null +++ b/src/icons/circle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/icons/music.svg b/src/icons/music.svg new file mode 100644 index 000000000..7bee2f7e1 --- /dev/null +++ b/src/icons/music.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/icons/polyline.svg b/src/icons/polyline.svg new file mode 100644 index 000000000..c282a5bd7 --- /dev/null +++ b/src/icons/polyline.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/icons/volume-2.svg b/src/icons/volume-2.svg new file mode 100644 index 000000000..03d521c7e --- /dev/null +++ b/src/icons/volume-2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/js/projectMigrationScripts/1.8.0.js b/src/js/projectMigrationScripts/1.8.0.js index b7fa9902c..8a7c99c2e 100644 --- a/src/js/projectMigrationScripts/1.8.0.js +++ b/src/js/projectMigrationScripts/1.8.0.js @@ -4,6 +4,15 @@ window.migrationProcess.push({ version: '1.8.0', process: project => new Promise(resolve => { project.contentTypes = project.contentTypes || []; + project.groups = project.groups || { + fonts: [], + textures: [], + styles: [], + rooms: [], + types: [], + sounds: [], + emitterTandems: [] + }; resolve(); }) diff --git a/src/node_requires/resources/IAsset.d.ts b/src/node_requires/resources/IAsset.d.ts new file mode 100644 index 000000000..76b82c01c --- /dev/null +++ b/src/node_requires/resources/IAsset.d.ts @@ -0,0 +1,5 @@ +interface IAsset { + readonly type: resourceType; + readonly uid: string; + lastmod: number; +} diff --git a/src/node_requires/resources/commonTypes.d.ts b/src/node_requires/resources/commonTypes.d.ts new file mode 100644 index 000000000..153693ac9 --- /dev/null +++ b/src/node_requires/resources/commonTypes.d.ts @@ -0,0 +1,5 @@ +type resourceType = 'type' | 'room' | 'sound' | 'style' | 'skeleton' | 'texture' | 'tandem'; + +type fontWeight = '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900'; + +type assetRef = -1 | string; // Either an empty string or a UID diff --git a/src/node_requires/resources/fonts/index.js b/src/node_requires/resources/fonts/index.js index bd71c5bf6..884671949 100644 --- a/src/node_requires/resources/fonts/index.js +++ b/src/node_requires/resources/fonts/index.js @@ -61,7 +61,7 @@ const fontGenPreview = async function fontGenPreview(font) { await fs.writeFile(getFontPreview(font, true), buf); }; -const importTtfToFont = async function importTtfToFont(src) { +const importTtfToFont = async function importTtfToFont(src, group) { const fs = require('fs-extra'), path = require('path'); if (path.extname(src).toLowerCase() !== '.ttf') { @@ -81,6 +81,7 @@ const importTtfToFont = async function importTtfToFont(src) { bitmapFontLineHeight: 18, charsets: ['allInFont'], customCharset: '', + group, uid }; global.currentProject.fonts.push(obj); diff --git a/src/node_requires/resources/projects/index.ts b/src/node_requires/resources/projects/index.ts index b7ab78b7c..824eefddf 100644 --- a/src/node_requires/resources/projects/index.ts +++ b/src/node_requires/resources/projects/index.ts @@ -59,7 +59,7 @@ const getProjectIct = function (projPath: string): string { return projPath; }; -module.exports = { +export { defaultProject, getDefaultProjectDir, getProjectThumbnail, diff --git a/src/node_requires/resources/rooms/IRoom.d.ts b/src/node_requires/resources/rooms/IRoom.d.ts new file mode 100644 index 000000000..89904b167 --- /dev/null +++ b/src/node_requires/resources/rooms/IRoom.d.ts @@ -0,0 +1,54 @@ +type canvasPatternRepeat = 'repeat' | 'repeat-x' | 'repeat-y' | 'no-repeat'; + +interface IRoomBackground { + depth: number, + texture: assetRef, + extends: { + parallaxX?: number, + parallaxY?: number, + shiftX?: number, + shiftY?: number, + repeat: canvasPatternRepeat + [key: string]: unknown + } +} + +interface IRoomCopy { + x: number, + y: number, + uid: assetRef, + tx?: number, + ty?: number, + exts: { + [key: string]: unknown + } +} + +interface ITileTemplate { + x: number; + y: number; +} + +interface ITileLayerTemplate { + depth: number; + tiles: Array +} + +interface IRoom extends IAsset { + // Currently just stick to the old structure + width: number, + height: number, + backgrounds: Array, + copies: Array, + tiles: Array + gridX: number, + gridY: number, + oncreate: string, + onstep: string, + ondraw: string, + onleave: string, + thumbnail: string, + extends: { + [key: string]: unknown + } +} diff --git a/src/node_requires/resources/rooms/defaultRoom.ts b/src/node_requires/resources/rooms/defaultRoom.ts new file mode 100644 index 000000000..493c760e1 --- /dev/null +++ b/src/node_requires/resources/rooms/defaultRoom.ts @@ -0,0 +1,30 @@ +const generateGUID = require('./../../generateGUID'); + +const room = { + type: 'room' as resourceType, + oncreate: '', + onstep: '', + ondraw: '', + onleave: '', + gridX: 64, + gridY: 64, + width: 1280, + height: 720 +}; + +const get = function (): IRoom { + const uid = generateGUID(); + const newRoom = Object.assign({}, room, { + name: 'Room_' + uid.slice(-6), + backgrounds: [], + copies: [], + tiles: [], + extends: {}, + lastmod: Number(new Date()), + thumbnail: uid, + uid + }); + return newRoom; +}; + +export {get}; diff --git a/src/node_requires/resources/rooms/index.ts b/src/node_requires/resources/rooms/index.ts new file mode 100644 index 000000000..93b221eb7 --- /dev/null +++ b/src/node_requires/resources/rooms/index.ts @@ -0,0 +1,55 @@ +const getDefaultRoom = require('./defaultRoom').get; +const fs = require('fs-extra'); +const path = require('path'); + +const createNewRoom = async function createNewRoom(name: string): Promise { + const room = getDefaultRoom(); + await fs.copy('./data/img/notexture.png', path.join((global as any).projdir, '/img/r' + room.uid + '.png')); + if (name) { + room.name = String(name); + } + window.currentProject.rooms.push(room); + window.signals.trigger('roomsChanged'); + return room; +}; + +/** + * Gets the ct.js room object by its id. + * @param {string} id The id of the ct.js room + * @returns {IRoom} The ct.js room object + */ +const getRoomFromId = function getRoomFromId(id: string): IRoom { + const room = global.currentProject.rooms.find((r: IRoom) => r.uid === id); + if (!room) { + throw new Error(`Attempt to get a non-existent room with ID ${id}`); + } + return room; +}; + +/** + * Retrieves the full path to a thumbnail of a given room. + * @param {string|IRoom} room Either the id of the room, or its ct.js object + * @param {boolean} [x2] If set to true, returns a 340x256 image instead of 64x64. + * (Not implemented, actually!) + * @param {boolean} [fs] If set to true, returns a file system path, not a URI. + * @returns {string} The full path to the thumbnail. + */ +const getRoomPreview = (room: assetRef | IRoom, x2: boolean, fs: boolean): string => { + void x2; + if (room === -1) { + return 'data/img/notexture.png'; + } + if (typeof room === 'string') { + room = getRoomFromId(room); + } + if (fs) { + return `${(global as any).projdir}/img/r${room.thumbnail}.png`; + } + return `file://${(global as any).projdir}/img/r${room.thumbnail}.png?${room.lastmod}`; +}; + +export { + createNewRoom, + getRoomFromId, + getRoomPreview +}; diff --git a/src/node_requires/resources/skeletons.js b/src/node_requires/resources/skeletons.js index 9fd27b91e..13cd5a1a4 100644 --- a/src/node_requires/resources/skeletons.js +++ b/src/node_requires/resources/skeletons.js @@ -73,7 +73,7 @@ const skeletonGenPreview = function (skeleton) { }); }; -const importSkeleton = async function importSkeleton(source) { +const importSkeleton = async function importSkeleton(source, group) { const generateGUID = require('./../generateGUID'); const fs = require('fs-extra'); diff --git a/src/node_requires/resources/textures/index.js b/src/node_requires/resources/textures/index.js index 1ac9f7cd6..e7c617568 100644 --- a/src/node_requires/resources/textures/index.js +++ b/src/node_requires/resources/textures/index.js @@ -201,7 +201,7 @@ const isBgPostfixTester = /@bg$/; * @returns {Promise} A promise that resolves into the resulting texture object. */ // eslint-disable-next-line max-lines-per-function -const importImageToTexture = async (src, name) => { +const importImageToTexture = async (src, name, group) => { const fs = require('fs-extra'), path = require('path'), generateGUID = require('./../../generateGUID'); @@ -256,7 +256,8 @@ const importImageToTexture = async (src, name) => { top: 0, bottom: image.height, uid: id, - padding: 1 + padding: 1, + group }; if (!(src instanceof Buffer)) { obj.source = src; diff --git a/src/riotTags/dnd-processor.tag b/src/riotTags/dnd-processor.tag index 2e5b90721..e502965ee 100644 --- a/src/riotTags/dnd-processor.tag +++ b/src/riotTags/dnd-processor.tag @@ -46,6 +46,8 @@ dnd-processor e.stopPropagation(); }; this.onDrop = e => { + this.dropping = false; + this.update(); e.stopPropagation(); }; this.onDragLeave = e => { @@ -65,4 +67,4 @@ dnd-processor document.removeEventListener('dragover', this.onDragOver); document.removeEventListener('dragleave', this.onDragLeave); document.removeEventListener('drop', this.onDrop); - }); \ No newline at end of file + }); diff --git a/src/riotTags/fonts-panel.tag b/src/riotTags/fonts-panel.tag index 0724e0d67..cb6e0f49b 100644 --- a/src/riotTags/fonts-panel.tag +++ b/src/riotTags/fonts-panel.tag @@ -2,14 +2,14 @@ fonts-panel.flexfix.tall.fifty asset-viewer( collection="{global.currentProject.fonts}" contextmenu="{onFontContextMenu}" - vocspace="fonts" namespace="fonts" + assettype="fonts" click="{openFont}" thumbnails="{thumbnails}" names="{names}" ref="fonts" class="tall" - h1 {voc.fonts} + ) h1 {parent.voc.fonts} .toleft label.file.flexfix-header @@ -18,7 +18,7 @@ fonts-panel.flexfix.tall.fifty onchange="{parent.fontImport}") .button svg.feather - span {voc.import} + use(xlink:href="#download") span {parent.voc.import} context-menu(menu="{fontMenu}" ref="fontMenu") font-editor(if="{editingFont}" fontobj="{editedFont}") @@ -116,7 +116,7 @@ fonts-panel.flexfix.tall.fifty const {importTtfToFont} = require('./data/node_requires/resources/fonts'); for (let i = 0; i < files.length; i++) { if (/\.ttf/gi.test(files[i])) { - importTtfToFont(files[i]) + importTtfToFont(files[i], this.refs.fonts.currentGroup.uid) .then(() => { this.refs.fonts.updateList(); this.update(); diff --git a/src/riotTags/main-menu/icon-panel.tag b/src/riotTags/main-menu/icon-panel.tag index 44ebe8f78..fa1e3202d 100644 --- a/src/riotTags/main-menu/icon-panel.tag +++ b/src/riotTags/main-menu/icon-panel.tag @@ -3,14 +3,16 @@ icon-panel.aView.pad button(onclick="{opts.onclose}") {vocGlob.close} .clear ul.Cards - li( + li.aCard( each="{icon in iconList}" onclick="{copy(icon)}" no-reorder ) - span {icon} - svg.feather - use(xlink:href="#{icon}") + .aCard-aThumbnail + svg.feather + use(xlink:href="#{icon}") + .aCard-Properties + span {icon} script. this.namespace = 'common'; this.mixin(window.riotVoc); diff --git a/src/riotTags/particles/fx-panel.tag b/src/riotTags/particles/fx-panel.tag index 01cc5f771..ced68d4da 100644 --- a/src/riotTags/particles/fx-panel.tag +++ b/src/riotTags/particles/fx-panel.tag @@ -2,13 +2,14 @@ fx-panel.aPanel.aView asset-viewer.tall( collection="{global.currentProject.emitterTandems}" contextmenu="{showTandemPopup}" - vocspace="particleEmitters" namespace="emitterTandems" + assettype="emitterTandems" click="{openTandem}" + useicons="yup" thumbnails="{thumbnails}" ref="emitterTandems" ) - h1.nmt {voc.emittersHeading} + h1.nmt {parent.voc.emittersHeading} button(onclick="{parent.emitterTandemCreate}" title="Control+N" data-hotkey="Control+n") svg.feather use(xlink:href="#plus") @@ -19,7 +20,7 @@ fx-panel.aPanel.aView this.namespace = 'particleEmitters'; this.mixin(window.riotVoc); - this.thumbnails = () => 'data/img/particles.png'; + this.thumbnails = () => 'sparkles'; // Technically we edit a number of emitters at once — a "tandem", // but to not overcomplicate it all, let's call them "emitters" in UI anyways. @@ -44,7 +45,8 @@ fx-panel.aPanel.aView const tandem = { name: 'Tandem_' + slice, origname: 'pt' + slice, - emitters: [defaultEmitter] + emitters: [defaultEmitter], + group: this.refs.emitterTandems.currentGroup.uid }; global.currentProject.emitterTandems.push(tandem); diff --git a/src/riotTags/project-selector.tag b/src/riotTags/project-selector.tag index 6923257bf..71e74493d 100644 --- a/src/riotTags/project-selector.tag +++ b/src/riotTags/project-selector.tag @@ -22,13 +22,15 @@ project-selector onclick="{loadRecentProject}" title="{project}" ) - img(src="{getProjectThumbnail(project)}") - span {getProjectName(project)} + .aCard-aThumbnail + img(src="{getProjectThumbnail(project)}") + .aCard-Properties + span {getProjectName(project)} .aCard-Actions - button.tiny(onclick="{cloneProject}" title="{voc.cloneProject}") + button.tiny.forcebackground(onclick="{cloneProject}" title="{voc.cloneProject}") svg.feather use(xlink:href="#copy") - button.tiny(onclick="{forgetProject}" title="{voc.forgetProject}") + button.tiny.forcebackground(onclick="{forgetProject}" title="{voc.forgetProject}") svg.feather use(xlink:href="#x") .flexfix-body.pad(show="{tab === 'examples'}") @@ -46,9 +48,9 @@ project-selector onclick="{loadRecentProject}" title="{project}" ) - .Cards-aThumbnail + .aCard-aThumbnail img(src="{getProjectThumbnail(project)}") - .Cards-Properties + .aCard-Properties span {getProjectName(project)} .aCard-Actions button.tiny(onclick="{cloneProject}" title="{voc.cloneProject}") diff --git a/src/riotTags/project-settings/tabs/rendering-settings.tag b/src/riotTags/project-settings/tabs/rendering-settings.tag index 946b8d84b..c0b2c2aa9 100644 --- a/src/riotTags/project-settings/tabs/rendering-settings.tag +++ b/src/riotTags/project-settings/tabs/rendering-settings.tag @@ -12,9 +12,6 @@ rendering-settings label.block.checkbox input(type="checkbox" value="{renderSettings.highDensity}" checked="{renderSettings.highDensity}" onchange="{wire('this.renderSettings.highDensity')}") span {voc.highDensity} - label.block.checkbox - input(type="checkbox" value="{renderSettings.usePixiLegacy}" checked="{renderSettings.usePixiLegacy}" onchange="{wire('this.renderSettings.usePixiLegacy')}") - span {voc.usePixiLegacy} fieldset label.block.checkbox input(type="checkbox" checked="{renderSettings.hideCursor}" onchange="{wire('this.renderSettings.hideCursor')}") @@ -32,4 +29,4 @@ rendering-settings this.mixin(window.riotVoc); this.mixin(window.riotWired); this.currentProject = global.currentProject; - this.renderSettings = this.currentProject.settings.rendering; \ No newline at end of file + this.renderSettings = this.currentProject.settings.rendering; diff --git a/src/riotTags/rooms/rooms-panel.tag b/src/riotTags/rooms/rooms-panel.tag index ba3112f5e..a8afca1ff 100644 --- a/src/riotTags/rooms/rooms-panel.tag +++ b/src/riotTags/rooms/rooms-panel.tag @@ -1,40 +1,20 @@ rooms-panel.aPanel.aView - .flexfix.tall - .flexfix-header - div - .toright - b {vocGlob.sort} - button.inline.square(onclick="{switchSort('date')}" class="{selected: sort === 'date' && !searchResults}") - svg.feather - use(xlink:href="#clock") - button.inline.square(onclick="{switchSort('name')}" class="{selected: sort === 'name' && !searchResults}") - svg.feather - use(xlink:href="#sort-alphabetically") - .aSearchWrap - input.inline(type="text" onkeyup="{fuseSearch}") - svg.feather - use(xlink:href="#search") - button.inline.square(onclick="{switchLayout}") - svg.feather - use(xlink:href="#{localStorage.roomsLayout === 'list'? 'grid' : 'list'}") - .toleft - button#roomcreate(onclick="{roomCreate}" data-hotkey="Control+n" title="Control+N") - svg.feather - use(xlink:href="#plus") - span {voc.create} - ul.pad.Cards.largeicons.flexfix-body(class="{list: localStorage.roomsLayout === 'list'}") - li( - each="{room in (searchResults? searchResults : rooms)}" - class="{starting: global.currentProject.startroom === room.uid}" - onclick="{openRoom(room)}" - oncontextmenu="{menuPopup(room)}" - onlong-press="{menuPopup(room)}" - ) - img(src="file://{global.projdir + '/img/r' + room.thumbnail + '.png?' + room.lastmod}") - span {room.name} - span.date(if="{room.lastmod}") {niceTime(room.lastmod)} - svg.feather.aStartingRoomIcon(if="{global.currentProject.startroom === room.uid}") - use(xlink:href="#play") + asset-viewer( + collection="{global.currentProject.rooms}" + contextmenu="{menuPopup}" + namespace="rooms" + assettype="rooms" + defaultlayout="largeCards" + click="{openRoom}" + thumbnails="{thumbnails}" + icons="{icons}" + ref="rooms" + class="tall" + ) + button#roomcreate(onclick="{parent.roomCreate}" data-hotkey="Control+n" title="Control+N") + svg.feather + use(xlink:href="#plus") + span {parent.voc.create} room-editor(if="{editing}" room="{editingRoom}") context-menu(menu="{roomMenu}" ref="roomMenu") script. @@ -44,92 +24,20 @@ rooms-panel.aPanel.aView this.mixin(window.riotVoc); this.mixin(window.riotNiceTime); this.editing = false; - this.sort = 'name'; - this.sortReverse = false; - this.updateList = () => { - this.rooms = [...global.currentProject.rooms]; - if (this.sort === 'name') { - this.rooms.sort((a, b) => a.name.localeCompare(b.name)); - } else { - this.rooms.sort((a, b) => b.lastmod - a.lastmod); - } - if (this.sortReverse) { - this.rooms.reverse(); - } - }; - this.switchSort = sort => () => { - if (this.sort === sort) { - this.sortReverse = !this.sortReverse; - } else { - this.sort = sort; - this.sortReverse = false; - } - this.updateList(); - }; - this.switchLayout = () => { - localStorage.roomsLayout = localStorage.roomsLayout === 'list' ? 'grid' : 'list'; - }; - const fuseOptions = { - shouldSort: true, - tokenize: true, - threshold: 0.5, - location: 0, - distance: 100, - maxPatternLength: 32, - minMatchCharLength: 1, - keys: ['name'] - }; - const Fuse = require('fuse.js'); - this.fuseSearch = e => { - if (e.target.value.trim()) { - var fuse = new Fuse(this.rooms, fuseOptions); - this.searchResults = fuse.search(e.target.value.trim()); - } else { - this.searchResults = null; - } - }; - this.setUpPanel = () => { - this.updateList(); - this.searchResults = null; - this.editing = false; - this.editingRoom = null; - this.update(); - }; - window.signals.on('projectLoaded', this.setUpPanel); - this.on('mount', this.setUpPanel); - this.on('unmount', () => { - window.signals.off('projectLoaded', this.setUpPanel); - }); - - const fs = require('fs-extra'), - path = require('path'); - this.roomCreate = function roomCreate() { + const fs = require('fs-extra'); + const {createNewRoom} = require('./data/node_requires/resources/rooms'); + this.roomCreate = async () => { if (this.editing) { return false; } - var guid = generateGUID(), - thumbnail = guid; - fs.copy('./data/img/notexture.png', path.join(global.projdir, '/img/r' + thumbnail + '.png'), () => { - var newRoom = { - name: 'Room_' + guid.slice(-6), - oncreate: '', - onstep: '', - ondraw: '', - onleave: '', - width: 800, - height: 600, - backgrounds: [], - copies: [], - tiles: [], - uid: guid, - thumbnail - }; - global.currentProject.rooms.push(newRoom); - this.openRoom(newRoom)(); - this.updateList(); - this.update(); - }); + const newRoom = await createNewRoom(); + if (!this.refs.rooms.currentGroup.isUngroupedGroup) { + newRoom.group = this.refs.rooms.currentGroup.uid; + } + this.openRoom(newRoom)(); + this.refs.rooms.updateList(); + this.update(); return true; }; this.openRoom = room => () => { @@ -139,6 +47,7 @@ rooms-panel.aPanel.aView this.roomMenu = { items: [{ + icon: 'play', label: this.voc.makestarting, click: () => { global.currentProject.startroom = this.editingRoom.uid; @@ -171,7 +80,7 @@ rooms-panel.aPanel.aView newRoom.uid = guid; newRoom.thumbnail = thumbnail; fs.linkSync(global.projdir + '/img/r' + this.editingRoom.thumbnail + '.png', global.projdir + '/img/r' + thumbnail + '.png'); - this.updateList(); + this.refs.rooms.updateList(); this.update(); } }); @@ -201,7 +110,7 @@ rooms-panel.aPanel.aView if (e.buttonClicked === 'ok') { var ind = global.currentProject.rooms.indexOf(this.editingRoom); global.currentProject.rooms.splice(ind, 1); - this.updateList(); + this.refs.rooms.updateList(); this.update(); window.alertify .okBtn(window.languageJSON.common.ok) @@ -217,3 +126,23 @@ rooms-panel.aPanel.aView this.refs.roomMenu.popup(e.clientX, e.clientY); e.preventDefault(); }; + this.thumbnails = require('./data/node_requires/resources/rooms').getRoomPreview; + this.icons = room => { + const icons = []; + if (room.uid === window.currentProject.startroom) { + icons.push('play'); + } + if (room.oncreate.trim()) { + icons.push('sun'); + } + if (room.onstep.trim()) { + icons.push('skip-forward'); + } + if (room.ondraw.trim()) { + icons.push('edit-2'); + } + if (room.onleave.trim()) { + icons.push('trash'); + } + return icons; + }; diff --git a/src/riotTags/shared/asset-viewer.tag b/src/riotTags/shared/asset-viewer.tag index 53b9c455a..a14a610a2 100644 --- a/src/riotTags/shared/asset-viewer.tag +++ b/src/riotTags/shared/asset-viewer.tag @@ -1,5 +1,5 @@ // - A generic asset viewer with a search form, sorting and switchable layout + A generic asset viewer with a search form, sorting, grouping, and switchable layout. @slot Can use nested tags. Yields the passed markup as a header of an asset viewer. @@ -9,11 +9,12 @@ @attribute namespace (string) A unique namespace used to store settings. Fallbacks to 'default'. - @attribute vocspace (string) - A namespace in the current language file to use for translated tags in a passed slot. - Not needed if no nested markup was passed to the tag. - Fallbacks to `namespace` attribute. + @attribute defaultlayout (string) + The default listing layout used if the user has not selected one yet. + Can be "cards", "list", "largeCards". + @attribute assettype (string) + The type of assets shown. The attribute is needed for groups to function. @attribute collection (riot function) A collection of items to iterate over while generating markup, sorting and firing events. @attribute names (riot function) @@ -21,6 +22,16 @@ Fallbacks to `item.name` if not defined. @attribute thumbnails (riot function) A mapping funtion that takes a collection object and returns a url for its thumbnail. + The function is passed with the collection object, `true` or `false` depending on whether + the large-card view is active, and `false`, which match the arguments used to get + a URL for the thumbnail of the proper size in many get{X}Preview methods. + @attribute useicons (atomic) + Tells the asset viewer to use SVG icons instead of img tag. + The `thumbnails` function should then return the name of the SVG icon. + + @attribute icons (riot function) + A mapping funtion that takes a collection object and returns an array of icon names. + The icons are shown near the asset's name and are used to convey some metadata. @attribute click (riot function) A two-fold callback (item => e => {…}) fired when a user clicks on an item, passing the associated collection object as its only argument in the first function, @@ -33,6 +44,11 @@ @method updateList() Update the asset viewer, needed e.g. when new items were added. + @property currentGroup (object) + The current selected group. Will have `isUngroupedGroup` property when the "ungrouped" + category is selected. Otherwise, groups are meant to be distinguished by their + `uid` property. + asset-viewer.flexfix(class="{opts.namespace} {opts.class}") .flexfix-header .toright @@ -55,32 +71,114 @@ asset-viewer.flexfix(class="{opts.namespace} {opts.class}") use(xlink:href="#search") button.inline.square(onclick="{switchLayout}") svg.feather - use(xlink:href="#{localStorage[opts.namespace? (opts.namespace+'Layout') : 'defaultAssetLayout'] === 'list'? 'grid' : 'list'}") + use(xlink:href="#{layoutToIconMap[currentLayout]}") + button.inline.nogrow(onclick="{addNewGroup}") + svg.feather + use(xlink:href="#folder-plus") + span {voc.addNewGroup} .toleft .clear .flexfix-body - ul.Cards(class="{list: localStorage[opts.namespace? (opts.namespace+'Layout') : 'defaultAssetLayout'] === 'list'}") - li( - each="{asset in (searchResults? searchResults : collection)}" + .aSpacer + ul.Cards.nmt( + if="{opts.assettype}" + ) + li.aCard( + onclick="{openGroup({isUngroupedGroup: true})}" + class="{active: currentGroup.isUngroupedGroup}" + ondrop="{onGroupDrop}" + ) + .aCard-aThumbnail + svg.feather.group-icon.act + use(xlink:href="#folder") + .aCard-Properties + span {voc.ungrouped} + li.aCard( + each="{group in currentProject.groups[opts.assettype]}" + oncontextmenu="{openGroupContextMenu(group)}" + onlong-press="{openGroupContextMenu(group)}" + onclick="{openGroup(group)}" + ondrop="{onGroupDrop}" + class="{active: currentGroup === group}" + no-reorder + ) + .aCard-aThumbnail + svg.feather.group-icon(class="{group.colorClass}") + use(xlink:href="#{group.icon}") + .aCard-Properties + span {group.name} + .center(if="{!(searchResults? searchResults : getGrouped(collection)).length}") + svg.anIllustration + use(xlink:href="data/img/weirdFoldersIllustration.svg#illustration") + br + span {vocGlob.nothingToShowFiller} + ul.Cards(class="{layoutToClassListMap[currentLayout]}") + li.aCard( + each="{asset in (searchResults? searchResults : getGrouped(collection))}" oncontextmenu="{parent.opts.contextmenu && parent.opts.contextmenu(asset)}" onlong-press="{parent.opts.contextmenu && parent.opts.contextmenu(asset)}" onclick="{parent.opts.click && parent.opts.click(asset)}" + draggable="{!!parent.opts.assettype}" + ondragstart="{parent.onItemDrag}" no-reorder ) - .Cards-aThumbnail - img(if="{parent.opts.thumbnails}" src="{parent.opts.thumbnails(asset)}") - .Cards-Properties + .aCard-aThumbnail + img( + if="{parent.opts.thumbnails && !parent.opts.useicons}" + src="{parent.opts.thumbnails(asset, currentLayout === 'largeCards', false)}" + ) + svg.feather.group-icon.act(if="{parent.opts.thumbnails && parent.opts.useicons}") + use(xlink:href="#{parent.opts.thumbnails(asset)}") + .aCard-Properties span {parent.opts.names? parent.opts.names(asset) : asset.name} + .asset-viewer-Icons(if="{parent.opts.icons}") + svg.feather(each="{icon in parent.opts.icons(asset)}" class="feather-{icon}") + use(xlink:href="#{icon}") span.date(if="{asset.lastmod}") {niceTime(asset.lastmod)} - img(if="{parent.opts.thumbnails}" src="{parent.opts.thumbnails(asset)}") + group-editor( + if="{showingGroupEditor}" + onapply="{closeGroupEditor}" + onclose="{closeGroupEditor}" + group="{editedGroup}" + ) + context-menu(menu="{groupContextMenu}" ref="groupMenu") script. - this.namespace = this.opts.vocspace || this.opts.namespace; + this.namespace = 'assetViewer'; this.mixin(window.riotVoc); this.mixin(window.riotNiceTime); this.sort = 'name'; this.sortReverse = false; + this.currentGroup = { + isUngroupedGroup: true + }; + + const layouts = ['cards', 'largeCards', 'list']; + this.layoutToIconMap = { + cards: 'layout-cards', + list: 'menu', + largeCards: 'grid' + }; + this.layoutToClassListMap = { + cards: 'cards', + list: 'list', + largeCards: 'largeicons' + }; + this.currentLayout = localStorage.defaultAssetLayout || 'cards'; + const n = this.opts.namespace; + if (n && localStorage[n + 'Layout']) { + this.currentLayout = localStorage[n + 'Layout']; + } else if (this.opts.defaultlayout) { + setting = this.opts.defaultlayout; + } + this.switchLayout = () => { + const idx = (layouts.indexOf(this.currentLayout) + 1) % layouts.length; + this.currentLayout = layouts[idx]; + const key = this.opts.namespace ? (this.opts.namespace + 'Layout') : 'defaultAssetLayout'; + localStorage[key] = this.currentLayout; + }; + const fuseOptions = { shouldSort: true, tokenize: true, @@ -126,9 +224,106 @@ asset-viewer.flexfix(class="{opts.namespace} {opts.class}") this.searchResults = null; } }; - this.switchLayout = () => { - const key = this.opts.namespace ? (this.opts.namespace + 'Layout') : 'defaultAssetLayout'; - localStorage[key] = localStorage[key] === 'list' ? 'grid' : 'list'; - }; this.updateList(); + + this.getGrouped = collection => { + if (!this.opts.assettype) { + return collection; + } + return collection.filter(i => + this.opts.assettype && + (!i.group && this.currentGroup.isUngroupedGroup) || + (i.group === this.currentGroup.uid)) + }; + this.openGroup = group => () => { + this.currentGroup = group; + } + this.addNewGroup = () => { + if (!currentProject.groups) { + window.alertify('No groups initialized in this project. Please run `applyMigrationCode(\'1.8.0\')` for this project.'); + return false; + } + const at = this.opts.assettype; + if (!at) { + const errMessage = `Asset type not set for ${at} asset type.`; + window.alertify(errMessage); + throw new Error(errMessage); + } + const newGroup = { + name: this.voc.newGroupName, + icon: 'folder', + colorClass: 'act', + uid: require('./data/node_requires/generateGUID')() + }; + currentProject.groups[at].push(newGroup); + this.editedGroup = newGroup; + this.showingGroupEditor = true; + }; + this.closeGroupEditor = () => { + this.showingGroupEditor = false; + this.editedGroup = void 0; + this.update(); + }; + + this.groupContextMenu = { + items: [{ + label: this.vocGlob.edit, + icon: 'edit', + click: () => { + this.showingGroupEditor = true; + this.update(); + } + }, { + type: 'separator' + }, { + label: this.vocGlob.delete, + icon: 'trash', + click: () => { + const group = this.editedGroup, + oldId = this.editedGroup.uid; + window.alertify.confirm(this.voc.groupDeletionConfirmation) + .then(e => { + if (e.buttonClicked !== 'ok') { + return; + } + for (const asset of this.opts.collection) { + if (asset.group === oldId) { + delete asset.group; + } + } + const groups = currentProject.groups[this.opts.assettype]; + groups.splice(groups.indexOf(group), 1); + this.update(); + }); + } + }] + }; + this.openGroupContextMenu = group => e => { + e.preventDefault(); + this.editedGroup = group; + this.refs.groupMenu.popup(e.clientX, e.clientY); + }; + + this.onItemDrag = e => { + console.log(e); + if (!this.opts.assettype) { + return; + } + e.dataTransfer.setData('text/plain', `moveToGroup;${e.item.asset.uid}`); + e.dataTransfer.dropEffect = 'move'; + }; + this.onGroupDrop = e => { + const dt = e.dataTransfer.getData('text/plain'); + if (dt.split(';')[0] !== 'moveToGroup') { + return false; + } + const assetId = dt.split(';')[1]; + const asset = this.opts.collection.find(a => a.uid === assetId); + if (!e.item) { // this is "Ungrouped" tag which is not in a loop + delete asset.group; + } else { + const groupId = e.item.group.uid; + asset.group = groupId; + } + }; diff --git a/src/riotTags/shared/group-editor.tag b/src/riotTags/shared/group-editor.tag new file mode 100644 index 000000000..ca33c9ec5 --- /dev/null +++ b/src/riotTags/shared/group-editor.tag @@ -0,0 +1,58 @@ +// + A tag used to edit an asset group, allowing to change its name, color and icon. + Mutates the target object, returns nothing in its callbacks. + + @attribute onclose (riot function) + @attribute onapply (riot function) + + @attribute group (object) + The group that should be edited. + +group-editor.aDimmer + .aModal.pad.npb(ref="widget") + .toright + svg.feather.anActionableIcon(onclick="{opts.onclose}") + use(xlink:href="#x") + h1.nmt.npt {voc.groupEditor} + label.block + b {vocGlob.name} + br + input(type="text" value="{opts.group.name}" onchange="{wire('this.opts.group.name')}") + .label.block + b {voc.icon} + br + icon-input(val="{opts.group.icon}" onselected="{onIconSelected}") + .label.block + b {voc.color} + br + button.inline.square( + each="{colorClass in ['act', 'accent1', 'error', 'success', 'warning', 'text']}" + class="{active: parent.opts.group.colorClass === colorClass}" + onclick="{setColorClass(colorClass)}" + ) + svg.feather(class="{colorClass}") + use(xlink:href="#droplet") + .aSpacer + .inset + button.nmr(onclick="{opts.onapply}") + svg.feather + use(xlink:href="#check") + span {vocGlob.apply} + script. + this.namespace = 'groupEditor'; + this.mixin(window.riotVoc); + this.mixin(window.riotWired); + this.setColorClass = colorClass => () => { + this.opts.group.colorClass = colorClass; + }; + this.onIconSelected = icon => { + this.opts.group.icon = icon; + this.update(); + }; + this.onClose = () => { + Object.assign(this.opts.group, this.oldGroup); + if (this.opts.onclose) { + this.opts.onclose(); + } + }; + this.oldGroup = JSON.parse(JSON.stringify(this.opts.group)); diff --git a/src/riotTags/shared/icon-selector.tag b/src/riotTags/shared/icon-selector.tag index 3828b7a36..8949c50e5 100644 --- a/src/riotTags/shared/icon-selector.tag +++ b/src/riotTags/shared/icon-selector.tag @@ -16,14 +16,16 @@ icon-selector.aView.pad h1(if="{opts.header}") {opts.header} .clear ul.Cards - li( + li.aCard( each="{icon in iconList}" onclick="{parent.opts.onselected(icon)}" no-reorder ) - span {icon} - svg.feather - use(xlink:href="#{icon}") + .aCard-aThumbnail + svg.feather + use(xlink:href="#{icon}") + .aCard-Properties + span {icon} script. this.namespace = 'common'; this.mixin(window.riotVoc); diff --git a/src/riotTags/sounds/sound-editor.tag b/src/riotTags/sounds/sound-editor.tag index b5b7c9318..8de032a79 100644 --- a/src/riotTags/sounds/sound-editor.tag +++ b/src/riotTags/sounds/sound-editor.tag @@ -1,4 +1,4 @@ -sound-editor.aPanel.aView +sound-editor.aDimmer .aModal b {voc.name} br diff --git a/src/riotTags/sounds/sound-recorder.tag b/src/riotTags/sounds/sound-recorder.tag index b00beba75..3735bada4 100644 --- a/src/riotTags/sounds/sound-recorder.tag +++ b/src/riotTags/sounds/sound-recorder.tag @@ -1,4 +1,4 @@ -sound-recorder.aPanel.aView +sound-recorder.aDimmer .aModal.pad .toright button.square.inline(onclick="{opts.onclose}" title="{vocGlob.close}") @@ -143,11 +143,12 @@ sound-recorder.aPanel.aView const path = require('path'), fs = require('fs-extra'); const newSound = sounds.createNewSound(); + newSound.group = this.opts.group; const base64 = (await mp3Recorder.getBase64()).replace('data:audio/mp3;base64,', ''); const buffer = Buffer.from(base64, 'base64'); const temp = await require('./data/node_requires/platformUtils').getTempDir(); await fs.writeFile(path.join(temp.dir, 'recording.mp3'), buffer); - newSound.name = `Recording_${newSound.uid.split('-').pop()}`; + newSound.name = `Recording_${newSound.slice(-6)}`; await sounds.addSoundFile(newSound, path.join(temp.dir, 'recording.mp3')); temp.remove(); this.state = 'ready'; diff --git a/src/riotTags/sounds/sounds-panel.tag b/src/riotTags/sounds/sounds-panel.tag index 4fee4d50d..e00b97ca1 100644 --- a/src/riotTags/sounds/sounds-panel.tag +++ b/src/riotTags/sounds/sounds-panel.tag @@ -3,8 +3,10 @@ sounds-panel.aPanel.aView collection="{global.currentProject.sounds}" contextmenu="{popupMenu}" namespace="sounds" + assettype="sounds" click="{openSound}" thumbnails="{thumbnails}" + useicons="ye" ref="sounds" class="tall" ) @@ -17,7 +19,7 @@ sounds-panel.aPanel.aView use(xlink:href="#mic") span {parent.voc.record} sound-editor(if="{editing}" sound="{editedSound}") - sound-recorder(if="{recorderVisible}" onclose="{onCloseRecorder}") + sound-recorder(if="{recorderVisible}" onclose="{onCloseRecorder}" group="{refs.sounds.currentGroup.uid}") context-menu(menu="{soundMenu}" ref="soundMenu") script. this.namespace = 'sounds'; @@ -27,7 +29,7 @@ sounds-panel.aPanel.aView this.sortReverse = false; this.recorderVisible = false; - this.thumbnails = sound => `data/img/${sound.isMusic ? 'music' : 'wave'}.png`; + this.thumbnails = sound => sound.isMusic ? 'music' : 'volume-2'; this.setUpPanel = () => { this.searchResults = null; @@ -48,6 +50,9 @@ sounds-panel.aPanel.aView } const sounds = require('./data/node_requires/resources/sounds'); const newSound = sounds.createNewSound(); + if (!this.refs.sounds.currentGroup.isUngroupedGroup) { + newSound.group = this.refs.sounds.currentGroup.uid; + } this.refs.sounds.updateList(); this.openSound(newSound)(); return true; diff --git a/src/riotTags/style-editor.tag b/src/riotTags/style-editor.tag index 742f28b16..b315ca4b9 100644 --- a/src/riotTags/style-editor.tag +++ b/src/riotTags/style-editor.tag @@ -126,6 +126,18 @@ style-editor.aPanel.aView br input#shadowblur(type="number" value="{styleobj.shadow.blur}" min="0" onchange="{wire('this.styleobj.shadow.blur')}" oninput="{wire('this.styleobj.shadow.blur')}") .flexfix-footer + .aPanel.pad + .flexrow + h3 {voc.code} + .aSpacer + button.inline.nogrow(onclick="{copyCode}") + svg.feather + use(xlink:href="#copy") + span {voc.copyCode} + textarea.wide(disabled="true" ref="codeField") + | this.textLabel = new PIXI.Text('Your text here', ct.styles.get('{styleobj.name}')); + | {'\n'}this.addChild(this.textLabel); + button.wide.nogrow.noshrink(onclick="{styleSave}" title="Shift+Control+S" data-hotkey="Control+S") svg.feather use(xlink:href="#check") @@ -211,6 +223,11 @@ style-editor.aPanel.aView this.update(); }; + this.copyCode = () => { + nw.Clipboard.get().set(this.refs.codeField.value); + alertify.success(this.vocGlob.done); + }; + this.styleSetAlign = align => () => { this.styleobj.font.halign = align; }; diff --git a/src/riotTags/styles-panel.tag b/src/riotTags/styles-panel.tag index c0e118fa7..41e5b2419 100644 --- a/src/riotTags/styles-panel.tag +++ b/src/riotTags/styles-panel.tag @@ -3,6 +3,7 @@ styles-panel.tall.fifty collection="{global.currentProject.styles}" contextmenu="{onStyleContextMenu}" namespace="styles" + assettype="styles" click="{openStyle}" thumbnails="{thumbnails}" ref="styles" @@ -36,6 +37,9 @@ styles-panel.tall.fifty uid: id, origname: 's' + slice }; + if (!this.refs.styles.currentGroup.isUngroupedGroup) { + obj.group = this.refs.styles.currentGroup.uid; + } global.currentProject.styles.push(obj); this.editedStyle = obj; this.editingStyle = true; diff --git a/src/riotTags/textures/textures-panel.tag b/src/riotTags/textures/textures-panel.tag index 4b3e5031f..e0649db78 100644 --- a/src/riotTags/textures/textures-panel.tag +++ b/src/riotTags/textures/textures-panel.tag @@ -8,6 +8,7 @@ textures-panel.aPanel.aView namespace="textures" click="{openTexture}" thumbnails="{textureThumbnails}" + icons="{textureIcons}" ref="textures" ) label.file.inlineblock @@ -62,6 +63,12 @@ textures-panel.aPanel.aView this.dropping = false; this.textureThumbnails = require('./data/node_requires/resources/textures').getTexturePreview; + const iconMap = { + rect: 'square', + circle: 'circle', + strip: 'polyline' + }; + this.textureIcons = texture => [iconMap[texture.shape]]; this.skelThumbnails = require('./data/node_requires/resources/skeletons').getSkeletonPreview; this.fillTextureMap = () => { @@ -146,10 +153,10 @@ textures-panel.aPanel.aView const files = [...e.target.files].map(file => file.path); for (let i = 0; i < files.length; i++) { if (/\.(jpg|gif|png|jpeg)/gi.test(files[i])) { - importImageToTexture(files[i]); + importImageToTexture(files[i], void 0, this.refs.textures.currentGroup.uid); } else if (/_ske\.json/i.test(files[i])) { const {importSkeleton} = require('./data/node_requires/resources/skeletons'); - importSkeleton(files[i]); + importSkeleton(files[i], this.refs.skeletons.currentGroup.uid); } } e.srcElement.value = ''; @@ -165,7 +172,7 @@ textures-panel.aPanel.aView const imageBase64 = png.replace(/^data:image\/\w+;base64,/, ''); const imageBuffer = new Buffer(imageBase64, 'base64'); const {importImageToTexture} = require('./data/node_requires/resources/textures'); - importImageToTexture(imageBuffer); + importImageToTexture(imageBuffer, void 0, this.refs.textures.currentGroup.uid); alertify.success(this.vocGlob.pastedFromClipboard); }; diff --git a/src/riotTags/types-panel.tag b/src/riotTags/types-panel.tag index 1d9f05bd7..285b3dc81 100644 --- a/src/riotTags/types-panel.tag +++ b/src/riotTags/types-panel.tag @@ -3,8 +3,10 @@ types-panel.aPanel.aView collection="{global.currentProject.types}" contextmenu="{onTypeContextMenu}" namespace="types" + assettype="types" click="{openType}" thumbnails="{thumbnails}" + icons="{icons}" ref="types" class="tall" ) @@ -25,6 +27,22 @@ types-panel.aPanel.aView this.sortReverse = false; this.thumbnails = require('./data/node_requires/resources/types').getTypePreview; + this.icons = function(type) { + const icons = []; + if (type.oncreate.trim()) { + icons.push('sun'); + } + if (type.onstep.trim()) { + icons.push('skip-forward'); + } + if (type.ondraw.trim()) { + icons.push('edit-2'); + } + if (type.ondestroy.trim()) { + icons.push('trash'); + } + return icons; + }; this.setUpPanel = () => { this.fillTypeMap(); @@ -56,6 +74,9 @@ types-panel.aPanel.aView const typesAPI = require('./data/node_requires/resources/types/'); const type = typesAPI.createNewType(); + if (!this.refs.types.currentGroup.isUngroupedGroup) { + type.group = this.refs.types.currentGroup.uid; + } this.refs.types.updateList(); this.openType(type)(e); diff --git a/src/styl/buildingBlocks.styl b/src/styl/buildingBlocks.styl index bf1fc1c81..4362b88d4 100644 --- a/src/styl/buildingBlocks.styl +++ b/src/styl/buildingBlocks.styl @@ -12,6 +12,10 @@ width 100% height 100% z-index 20 + display flex + flex-flow column nowrap + align-items center + justify-content center .aModal, .aPanel box-sizing border-box @@ -21,12 +25,6 @@ position relative .aModal - position fixed - left 50% - top 50% - transform translate(-50%, -50%) translateZ(0) - filter blur(0px) - backface-visibility hidden max-height 90% overflow auto box-shadow 0 2px 5px rgba(0, 0, 0, 0.35) @@ -171,60 +169,9 @@ grid-template-columns repeat(auto-fill, minmax(17em, 1fr)) grid-gap 0.3em flex-grow 0 - & > li, .aCard - background background - padding 0.8em - margin 0 - box-sizing border-box - border 1px solid borderPale - border-radius 3px - display inline-block - vertical-align top - cursor pointer - transition 0.35s ease all - display flex - flex-flow row nowrap - align-items center - .Cards-aThumbnail - width 3rem - flex 0 0 3rem - margin-right 1rem - line-height 0 - svg - width 3rem - height 3rem - stroke-width 1 - // Images cheat a bit with negative margins - // to occupy a 4rem x 4rem box - img - width 4rem - height 4rem - max-width 4rem - margin -0.5rem 0 -0.5rem -0.5rem - border-radius br - &:hover - background rgba(act, 0.35) - .Cards-Properties - flex 1 1 auto - > .date - display none - span - font-family consolas, monospace - width 100% - display inline-block - vertical-align middle - text-overflow ellipsis - white-space nowrap - overflow hidden - &:hover, &.active - border-color acttext - &:active - border-color accent1 - background act - color background &.list display block - &.list li + &.list .aCard display flex flex-flow row nowrap margin 0 @@ -234,12 +181,16 @@ &:hover background act color background - span + .aCard-Properties flex 1 1 auto order 4 line-height 1.5rem text-align left - > .date + display flex + flex-flow row nowrap + .asset-viewer-Icons + flex 0 0 auto + .date display block flex 0 0 auto width 6rem @@ -249,13 +200,18 @@ order 10 & + li border-top 0 - & > img - float none - height 1.5rem + .aCard-aThumbnail width 1.5rem - margin 0 1rem 0 0 - flex 0 0 auto - order 2 + img, svg + height 1.5rem + width 1.5rem + margin 0 + flex 0 0 auto + order 2 + img + height 2rem + width 2rem + margin -0.25rem &:first-child border-top-left-radius br border-top-right-radius br @@ -268,19 +224,79 @@ text-align center position relative flex-flow column nowrap - img - float none - max-width 13.5em - max-height 10em - margin 0 - width auto - height auto + .aCard-aThumbnail + max-width 100% + width unset + margin-right 0 + svg + width 6rem + height 6rem + stroke-width 0.5 + img + width unset + height unset + max-width 100% + max-height 10rem + margin 0 span font-family consolas, monospace width 12.5em text-overflow ellipsis white-space nowrap overflow hidden +.aCard + background background + padding 0.8em + margin 0 + box-sizing border-box + border 1px solid borderPale + border-radius 3px + display inline-block + vertical-align top + cursor pointer + transition 0.35s ease all + display flex + flex-flow row nowrap + align-items center + &:hover, &.active + border-color acttext + &:active + border-color accent1 + background act + color background +.aCard-Properties + flex 1 1 auto + > .date + display none + span + font-family consolas, monospace + width 100% + display inline-block + vertical-align middle + text-overflow ellipsis + white-space nowrap + overflow hidden +.aCard-aThumbnail + width 3rem + flex 0 0 auto + margin-right 1rem + line-height 0 + svg + width 3rem + height 3rem + stroke-width 1 + color act + // Images cheat a bit with negative margins + // to occupy a 4rem x 4rem box + img + width 4rem + height 4rem + max-width 4rem + margin -0.5rem 0 -0.5rem -0.5rem + border-radius br + pointer-events none + &:hover img + background rgba(act, 0.35) .aCard-Actions position absolute top 0.5rem diff --git a/src/styl/common.styl b/src/styl/common.styl index 99b7c2cc0..3d55bb2c7 100644 --- a/src/styl/common.styl +++ b/src/styl/common.styl @@ -30,16 +30,18 @@ body bottom 0 position absolute -.act +.act.act color act -.accent1 +.accent1.accent1 color accent1 -.error +.error.error color error -.success +.success.success color success -.warning +.warning.warning color warning +.text.text + color text .borderall border 1px solid borderBright diff --git a/src/styl/inputs.styl b/src/styl/inputs.styl index 98843e632..6651b6fa0 100644 --- a/src/styl/inputs.styl +++ b/src/styl/inputs.styl @@ -323,7 +323,7 @@ thumbWidth = 1.5em position absolute label, .label - & + & + & + label, & + .label margin-top 0.5rem &.file display block diff --git a/src/styl/tags/rooms/rooms-panel.styl b/src/styl/tags/rooms/rooms-panel.styl index 49f689331..e4933c359 100644 --- a/src/styl/tags/rooms/rooms-panel.styl +++ b/src/styl/tags/rooms/rooms-panel.styl @@ -3,16 +3,7 @@ rooms-panel padding 1em overflow-x hidden - .Cards - .starting svg - position absolute - right 0.5em - top 0.5em + .asset-viewer-Icons + .feather-play.feather-play color warning - &.list - .starting svg - position static - margin 0 0.5rem - order 5 - img - width auto + opacity 1 diff --git a/src/styl/tags/shared/asset-viewer.styl b/src/styl/tags/shared/asset-viewer.styl index 108cad620..8678d7f31 100644 --- a/src/styl/tags/shared/asset-viewer.styl +++ b/src/styl/tags/shared/asset-viewer.styl @@ -8,11 +8,16 @@ asset-viewer filter invert(1) &:hover background transparent + &.types + .Cards.largeicons + .aCard-aThumbnail + img + max-height 8rem .Cards .&-Icons, .Cards .&-Icons color act - opacity 0.65 line-height 1rem svg + opacity 0.65 width 1rem height @width & + svg