mirror of
https://github.com/RetroShare/RSNewWebUI.git
synced 2026-09-14 11:05:47 +05:00
Merge pull request #84 from zelfroster/share-manager
Share manager for managing shared directories with different permissions
This commit is contained in:
commit
542a8c07bd
@ -139,6 +139,9 @@ const NewFileDialog = () => {
|
||||
};
|
||||
|
||||
const Component = () => {
|
||||
function clearFileCompleted() {
|
||||
rs.rsJsonApiRequest('/rsFiles/FileClearCompleted');
|
||||
}
|
||||
return {
|
||||
oninit: () => {
|
||||
Downloads.loadStrategy();
|
||||
@ -150,11 +153,7 @@ const Component = () => {
|
||||
m('h3', `Downloads (${Downloads.hashes ? Downloads.hashes.length : 0} files)`),
|
||||
m('.action', [
|
||||
m('button', { onclick: () => widget.popupMessage(m(NewFileDialog)) }, 'Add new file'),
|
||||
m(
|
||||
'button',
|
||||
{ onclick: () => rs.rsJsonApiRequest('/rsFiles/FileClearCompleted') },
|
||||
'Clear completed'
|
||||
),
|
||||
m('button', { onclick: clearFileCompleted }, 'Clear completed'),
|
||||
]),
|
||||
]),
|
||||
m('.widget__body-content', [
|
||||
|
||||
268
webui-src/app/files/files_manager.js
Normal file
268
webui-src/app/files/files_manager.js
Normal file
@ -0,0 +1,268 @@
|
||||
const m = require('mithril');
|
||||
const rs = require('rswebui');
|
||||
const widget = require('widgets');
|
||||
const futil = require('files/files_util');
|
||||
const cutil = require('config/config_util');
|
||||
|
||||
const shareManagerInfo = `
|
||||
This is a list of shared folders. You can add and remove folders using the buttons at the bottom.
|
||||
e.g- You can click on Edit button and then modify any field. When you add a new folder, initially
|
||||
all files in that folder are shared. You can separately share flags for each shared directory.
|
||||
`;
|
||||
|
||||
const accessTooltipText = [
|
||||
'Manage Control Access for Directories, The three options are for the following purpose.',
|
||||
m('i.fas.fa-search'),
|
||||
' Directory can be searched anonymously, ',
|
||||
m('i.fas.fa-download'),
|
||||
' Directory can be accessed anonymously, ',
|
||||
m('i.fas.fa-eye'),
|
||||
' Directory can be browsed by designated friends',
|
||||
];
|
||||
|
||||
const addNewDirInfo = `For Security reasons, Browsers don't allow to read directories so Please
|
||||
copy and paste the absolute path of the directory which you want to share.
|
||||
`;
|
||||
|
||||
let sharedDirArr = [];
|
||||
let isEditDisabled = true;
|
||||
|
||||
function loadSharedDirectories() {
|
||||
rs.rsJsonApiRequest('/rsFiles/getSharedDirectories').then((res) => {
|
||||
if (res.body.retval) sharedDirArr = res.body.dirs;
|
||||
});
|
||||
}
|
||||
|
||||
// Update Shared Directories when there is a corresponding event
|
||||
rs.events[rs.RsEventsType.SHARED_DIRECTORIES] = {
|
||||
handler: (event) => {
|
||||
console.log('Shared Directories Event: ', event);
|
||||
loadSharedDirectories();
|
||||
},
|
||||
};
|
||||
|
||||
const AddSharedDirForm = () => {
|
||||
let newDirPath = '';
|
||||
|
||||
function addNewSharedDirectory() {
|
||||
// check if newDirPath already exists
|
||||
const sharedDirArrExists = sharedDirArr.find((item) => item.filename === newDirPath);
|
||||
if (sharedDirArrExists) {
|
||||
alert('The path you entered already exists.');
|
||||
return;
|
||||
}
|
||||
const newSharedDir = {
|
||||
dir: {
|
||||
filename: newDirPath,
|
||||
virtualname: '',
|
||||
shareflags: futil.DIR_FLAGS_ANONYMOUS_SEARCH | futil.DIR_FLAGS_ANONYMOUS_DOWNLOAD,
|
||||
parent_groups: [],
|
||||
},
|
||||
};
|
||||
rs.rsJsonApiRequest('/rsFiles/addSharedDirectory', { ...newSharedDir }).then((res) => {
|
||||
if (res.body.retval) {
|
||||
loadSharedDirectories();
|
||||
}
|
||||
widget.popupMessage(
|
||||
m('.widget', [
|
||||
m('.widget__heading', m('h3', 'Add Shared Directory')),
|
||||
m(
|
||||
'.widget__body',
|
||||
m(
|
||||
'p',
|
||||
res.body.retval
|
||||
? 'Successfully Added Directory to Shared List'
|
||||
: 'Error in Adding Directory to Shared List'
|
||||
)
|
||||
),
|
||||
])
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
view: () =>
|
||||
m('.widget', [
|
||||
m('.widget__heading', m('h3', 'Add New Directory')),
|
||||
m('form.widget__body.share-manager__form', { onsubmit: addNewSharedDirectory }, [
|
||||
m('blockquote.info', addNewDirInfo),
|
||||
m('.share-manager__form_input', [
|
||||
m('label', 'Enter absolute directory path :'),
|
||||
m('input[type=text]', {
|
||||
value: newDirPath,
|
||||
oninput: (e) => (newDirPath = e.target.value),
|
||||
}),
|
||||
]),
|
||||
m('button[type=submit]', 'Add Directory'),
|
||||
]),
|
||||
]),
|
||||
};
|
||||
};
|
||||
|
||||
const ManageVisibility = () => {
|
||||
function handleSubmit() {
|
||||
m.redraw();
|
||||
const mContainer = document.getElementById('modal-container');
|
||||
mContainer.style.display = 'none';
|
||||
}
|
||||
return {
|
||||
view: (v) => {
|
||||
const { parentGroups } = v.attrs;
|
||||
return m('.widget', [
|
||||
m('.widget__heading', m('h3', 'Manage Visibility')),
|
||||
m('form.widget__body', { onsubmit: handleSubmit }, [
|
||||
Object.keys(futil.RsNodeGroupId).map((groupId) =>
|
||||
m('div.manage-visibility', [
|
||||
m(`label[for=${futil.RsNodeGroupId[groupId]}]`, futil.RsNodeGroupId[groupId]),
|
||||
m(`input[type=checkbox][id=${futil.RsNodeGroupId[groupId]}]`, {
|
||||
// if parentGroups is empty it means All friends nodes have Visibility
|
||||
checked: parentGroups.includes(groupId),
|
||||
onclick: () => {
|
||||
if (parentGroups.includes(groupId)) {
|
||||
const groupItemIndex = parentGroups.indexOf(groupId);
|
||||
parentGroups.splice(groupItemIndex, 1);
|
||||
} else {
|
||||
parentGroups.push(groupId);
|
||||
}
|
||||
},
|
||||
}),
|
||||
])
|
||||
),
|
||||
m('button[type=submit]', 'OK'),
|
||||
]),
|
||||
]);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const ShareDirTable = () => {
|
||||
return {
|
||||
oninit: futil.loadRsNodeGroupId,
|
||||
view: () => {
|
||||
return m('table.share-manager__table', [
|
||||
m(
|
||||
'thead.share-manager__table_heading',
|
||||
m('tr', [
|
||||
m('td', 'Shared Directories'),
|
||||
m('td', 'Visible Name'),
|
||||
m('td', 'Access', cutil.tooltip(accessTooltipText)),
|
||||
m('td', 'Visibility'),
|
||||
])
|
||||
),
|
||||
m(
|
||||
'tbody.share-manager__table_body',
|
||||
sharedDirArr.length &&
|
||||
sharedDirArr.map((sharedDirItem, index) => {
|
||||
const {
|
||||
filename,
|
||||
virtualname,
|
||||
shareflags,
|
||||
parent_groups: parentGroups,
|
||||
} = sharedDirItem;
|
||||
const sharedFlags = futil.calcIndividualFlags(shareflags);
|
||||
return m('tr', [
|
||||
m(
|
||||
'td',
|
||||
m('input[type=text]', {
|
||||
value: filename,
|
||||
disabled: isEditDisabled,
|
||||
oninput: (e) => {
|
||||
sharedDirArr[index].filename = e.target.value;
|
||||
},
|
||||
})
|
||||
),
|
||||
m(
|
||||
'td',
|
||||
m('input[type=text]', {
|
||||
value: virtualname,
|
||||
disabled: isEditDisabled,
|
||||
oninput: (e) => {
|
||||
sharedDirArr[index].virtualname = e.target.value;
|
||||
},
|
||||
})
|
||||
),
|
||||
m(
|
||||
'td.share-flags',
|
||||
Object.keys(sharedFlags).map((flag) => {
|
||||
return [
|
||||
m(`input.share-flags-check[type=checkbox][id=${flag}]`, {
|
||||
checked: sharedFlags[flag],
|
||||
disabled: isEditDisabled,
|
||||
}),
|
||||
m(
|
||||
`label.share-flags-label[for=${flag}]`,
|
||||
{
|
||||
onclick: () => {
|
||||
if (isEditDisabled) return;
|
||||
sharedFlags[flag] = !sharedFlags[flag];
|
||||
sharedDirArr[index].shareflags = futil.calcShareFlagsValue(sharedFlags);
|
||||
},
|
||||
style: isEditDisabled && { color: '#7D7D7D' },
|
||||
},
|
||||
m(
|
||||
// check the flag type then if its value is true then only render the icon
|
||||
flag === 'isAnonymousSearch'
|
||||
? sharedFlags[flag]
|
||||
? 'i.fas.fa-search'
|
||||
: 'span'
|
||||
: flag === 'isAnonymousDownload'
|
||||
? sharedFlags[flag]
|
||||
? 'i.fas.fa-download'
|
||||
: 'span'
|
||||
: sharedFlags[flag]
|
||||
? 'i.fas.fa-eye'
|
||||
: 'span'
|
||||
)
|
||||
),
|
||||
];
|
||||
})
|
||||
),
|
||||
m(
|
||||
'td',
|
||||
{
|
||||
// since this is not an input element, manually change color
|
||||
style: { color: isEditDisabled ? '#6D6D6D' : 'black' },
|
||||
onclick: () =>
|
||||
!isEditDisabled && widget.popupMessage(m(ManageVisibility, { parentGroups })),
|
||||
},
|
||||
parentGroups.length === 0
|
||||
? 'All Friend nodes'
|
||||
: parentGroups.map((groupFlag) => futil.RsNodeGroupId[groupFlag]).join(', ')
|
||||
),
|
||||
]);
|
||||
})
|
||||
),
|
||||
]);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const ShareManager = () => {
|
||||
function setNewSharedDirectories() {
|
||||
rs.rsJsonApiRequest('/rsFiles/setSharedDirectories', {
|
||||
dirs: sharedDirArr,
|
||||
});
|
||||
}
|
||||
return {
|
||||
oninit: loadSharedDirectories,
|
||||
view: () => {
|
||||
return m('.widget', [
|
||||
m('.widget__heading', m('h3', 'ShareManager')),
|
||||
m('form.widget__body.share-manager', { onsubmit: setNewSharedDirectories }, [
|
||||
m('blockquote.info', shareManagerInfo),
|
||||
m(ShareDirTable),
|
||||
m('.share-manager__actions', [
|
||||
m('button', { onclick: () => widget.popupMessage(m(AddSharedDirForm)) }, 'Add New'),
|
||||
m(
|
||||
'button',
|
||||
{ onclick: () => (isEditDisabled = !isEditDisabled) },
|
||||
isEditDisabled ? 'Edit' : 'Apply and Close'
|
||||
),
|
||||
]),
|
||||
]),
|
||||
]);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = ShareManager;
|
||||
@ -1,8 +1,8 @@
|
||||
const m = require('mithril');
|
||||
const rs = require('rswebui');
|
||||
const widget = require('widgets');
|
||||
const futil = require('files/files_util');
|
||||
const fproxy = require('files/files_proxy');
|
||||
const widget = require('widgets');
|
||||
|
||||
let matchString = '';
|
||||
let currentItem = 0;
|
||||
@ -21,24 +21,42 @@ function handleSubmit() {
|
||||
const SearchBar = () => {
|
||||
return {
|
||||
view: () =>
|
||||
m(
|
||||
'form.search-form',
|
||||
{
|
||||
onsubmit: handleSubmit,
|
||||
},
|
||||
[
|
||||
m('input[type=text][placeholder=search keyword]', {
|
||||
value: matchString,
|
||||
oninput: (e) => (matchString = e.target.value),
|
||||
}),
|
||||
m('button[type=submit]', m('i.fas.fa-search')),
|
||||
]
|
||||
),
|
||||
m('form.search-form', { onsubmit: handleSubmit }, [
|
||||
m('input[type=text][placeholder=search keyword]', {
|
||||
value: matchString,
|
||||
oninput: (e) => (matchString = e.target.value),
|
||||
}),
|
||||
m('button[type=submit]', m('i.fas.fa-search')),
|
||||
]),
|
||||
};
|
||||
};
|
||||
|
||||
const Layout = () => {
|
||||
let active = 0;
|
||||
function handleFileDownload(item) {
|
||||
rs.rsJsonApiRequest('/rsFiles/FileRequest', {
|
||||
fileName: item.fName,
|
||||
hash: item.fHash,
|
||||
flags: futil.RS_FILE_REQ_ANONYMOUS_ROUTING,
|
||||
size: {
|
||||
xstr64: item.fSize.xstr64,
|
||||
},
|
||||
})
|
||||
.then((res) => {
|
||||
widget.popupMessage(
|
||||
m('.widget', [
|
||||
m('.widget__heading', m('h3', m('i.fas.fa-file-medical'), ' File Download')),
|
||||
m(
|
||||
'.widget__body',
|
||||
m('p', `File is ${res.body.retval ? 'getting' : 'already'} downloaded.`)
|
||||
),
|
||||
])
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log('error in sending download request: ', error);
|
||||
});
|
||||
}
|
||||
return {
|
||||
view: () => [
|
||||
m('.widget__heading', [m('h3', 'Search'), m(SearchBar)]),
|
||||
@ -46,25 +64,26 @@ const Layout = () => {
|
||||
m('div.file-search-container', [
|
||||
m('div.file-search-container__keywords', [
|
||||
m('h5.bold', 'Keywords'),
|
||||
m(
|
||||
'div.keywords-container',
|
||||
Object.keys(reqObj)
|
||||
.reverse()
|
||||
.map((item, index) => {
|
||||
return m(
|
||||
m.route.Link,
|
||||
{
|
||||
class: active === index ? 'selected' : '',
|
||||
onclick: () => {
|
||||
active = index;
|
||||
currentItem = item;
|
||||
Object.keys(reqObj).length !== 0 &&
|
||||
m(
|
||||
'div.keywords-container',
|
||||
Object.keys(reqObj)
|
||||
.reverse()
|
||||
.map((item, index) => {
|
||||
return m(
|
||||
m.route.Link,
|
||||
{
|
||||
class: active === index ? 'selected' : '',
|
||||
onclick: () => {
|
||||
active = index;
|
||||
currentItem = item;
|
||||
},
|
||||
href: `/files/search/${item}`,
|
||||
},
|
||||
href: '/files/search/' + item,
|
||||
},
|
||||
reqObj[item]
|
||||
);
|
||||
})
|
||||
),
|
||||
reqObj[item]
|
||||
);
|
||||
})
|
||||
),
|
||||
]),
|
||||
m('div.file-search-container__results', [
|
||||
Object.keys(fproxy.fileProxyObj).length === 0
|
||||
@ -81,49 +100,19 @@ const Layout = () => {
|
||||
),
|
||||
m(
|
||||
'tbody.results',
|
||||
fproxy.fileProxyObj[currentItem.slice(1)] === undefined &&
|
||||
fproxy.fileProxyObj[currentItem.slice(1)].length === 0
|
||||
? 'Fetching Results...'
|
||||
: fproxy.fileProxyObj[currentItem.slice(1)].map((item) =>
|
||||
fproxy.fileProxyObj[currentItem.slice(1)]
|
||||
? fproxy.fileProxyObj[currentItem.slice(1)].map((item) =>
|
||||
m('tr', [
|
||||
m('td.results__name', [m('i.fas.fa-file'), m('span', item.fName)]),
|
||||
m('td.results__size', rs.formatBytes(item.fSize.xint64)),
|
||||
m('td.results__hash', item.fHash),
|
||||
m(
|
||||
'td.results__download',
|
||||
m(
|
||||
'button',
|
||||
{
|
||||
onclick: () => {
|
||||
rs.rsJsonApiRequest('/rsFiles/FileRequest', {
|
||||
fileName: item.fName,
|
||||
hash: item.fHash,
|
||||
flags: futil.RS_FILE_REQ_ANONYMOUS_ROUTING,
|
||||
size: {
|
||||
xstr64: item.fSize.xstr64,
|
||||
},
|
||||
})
|
||||
.then((res) => {
|
||||
res.retval
|
||||
? widget.popupMessage([
|
||||
m('i.fas.fa-file-medical'),
|
||||
m('h3', 'File is being downloaded!'),
|
||||
])
|
||||
: widget.popupMessage([
|
||||
m('i.fas.fa-file-medical'),
|
||||
m('h3', 'File is already downloaded!'),
|
||||
]);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log('error in sending download request: ', error);
|
||||
});
|
||||
},
|
||||
},
|
||||
'Download'
|
||||
)
|
||||
m('button', { onclick: () => handleFileDownload(item) }, 'Download')
|
||||
),
|
||||
])
|
||||
)
|
||||
: 'No Results.'
|
||||
),
|
||||
]),
|
||||
]),
|
||||
@ -134,7 +123,5 @@ const Layout = () => {
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
view: (vnode) => {
|
||||
return m(Layout);
|
||||
},
|
||||
view: () => m(Layout),
|
||||
};
|
||||
|
||||
@ -19,6 +19,59 @@ const RS_FILE_REQ_ANONYMOUS_ROUTING = 0x00000040;
|
||||
const RS_FILE_HINTS_REMOTE = 0x00000008;
|
||||
const RS_FILE_HINTS_LOCAL = 0x00000004;
|
||||
|
||||
// Flags for directory sharing permissions.
|
||||
const DIR_FLAGS_ANONYMOUS_SEARCH = 0x0800;
|
||||
const DIR_FLAGS_ANONYMOUS_DOWNLOAD = 0x0080;
|
||||
const DIR_FLAGS_BROWSABLE = 0x0400;
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
|
||||
// Access Permission calculated by performing OR operation on the above three flags.
|
||||
const DIR_FLAGS_PERMISSIONS_MASK =
|
||||
DIR_FLAGS_ANONYMOUS_SEARCH | DIR_FLAGS_ANONYMOUS_DOWNLOAD | DIR_FLAGS_BROWSABLE;
|
||||
|
||||
/* eslint-enable no-unused-vars */
|
||||
|
||||
// parent_groups visibility
|
||||
const RsNodeGroupId = {
|
||||
'00000000000000000000000000000001': 'Friends',
|
||||
'00000000000000000000000000000002': 'Family',
|
||||
'00000000000000000000000000000003': 'Co-Workers',
|
||||
'00000000000000000000000000000004': 'Other Contacts',
|
||||
'00000000000000000000000000000005': 'Favorites',
|
||||
};
|
||||
|
||||
function loadRsNodeGroupId() {
|
||||
rs.rsJsonApiRequest('/rsPeers/getGroupInfoList').then((res) => {
|
||||
const { groupInfoList } = res.body;
|
||||
groupInfoList.forEach((groupItem) => {
|
||||
if (!Object.prototype.hasOwnProperty.call(RsNodeGroupId, groupItem.id)) {
|
||||
RsNodeGroupId[groupItem.id] = groupItem.name;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function calcIndividualFlags(shareFlagsVal) {
|
||||
const isAnonymousSearch = (shareFlagsVal & DIR_FLAGS_ANONYMOUS_SEARCH) !== 0;
|
||||
const isAnonymousDownload = (shareFlagsVal & DIR_FLAGS_ANONYMOUS_DOWNLOAD) !== 0;
|
||||
const isBrowsable = (shareFlagsVal & DIR_FLAGS_BROWSABLE) !== 0;
|
||||
return {
|
||||
isAnonymousSearch,
|
||||
isAnonymousDownload,
|
||||
isBrowsable,
|
||||
};
|
||||
}
|
||||
|
||||
function calcShareFlagsValue(shareFlagsObj) {
|
||||
// calculate shareFlagsVal by performing OR operation on the Flags that have true value
|
||||
const shareFlagsVal =
|
||||
(shareFlagsObj.isAnonymousSearch && DIR_FLAGS_ANONYMOUS_SEARCH) |
|
||||
(shareFlagsObj.isAnonymousDownload && DIR_FLAGS_ANONYMOUS_DOWNLOAD) |
|
||||
(shareFlagsObj.isBrowsable && DIR_FLAGS_BROWSABLE);
|
||||
return shareFlagsVal;
|
||||
}
|
||||
|
||||
const createArrayProxy = (arr, onChange) => {
|
||||
return new Proxy(arr, {
|
||||
set: (target, property, value, reciever) => {
|
||||
@ -235,6 +288,7 @@ function compareArrays(big, small) {
|
||||
return !this.has(val);
|
||||
}, new Set(small));
|
||||
}
|
||||
|
||||
const MyFilesTable = () => {
|
||||
return {
|
||||
view: (v) =>
|
||||
@ -244,6 +298,7 @@ const MyFilesTable = () => {
|
||||
]),
|
||||
};
|
||||
};
|
||||
|
||||
const FriendsFilesTable = () => {
|
||||
return {
|
||||
view: (v) =>
|
||||
@ -274,10 +329,17 @@ module.exports = {
|
||||
RS_FILE_REQ_ANONYMOUS_ROUTING,
|
||||
RS_FILE_HINTS_REMOTE,
|
||||
RS_FILE_HINTS_LOCAL,
|
||||
DIR_FLAGS_ANONYMOUS_SEARCH,
|
||||
DIR_FLAGS_ANONYMOUS_DOWNLOAD,
|
||||
DIR_FLAGS_BROWSABLE,
|
||||
RsNodeGroupId,
|
||||
loadRsNodeGroupId,
|
||||
File,
|
||||
SearchBar,
|
||||
compareArrays,
|
||||
MyFilesTable,
|
||||
FriendsFilesTable,
|
||||
createProxy,
|
||||
calcIndividualFlags,
|
||||
calcShareFlagsValue,
|
||||
};
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
const m = require('mithril');
|
||||
const rs = require('rswebui');
|
||||
const util = require('files/files_util');
|
||||
const manager = require('files/files_manager');
|
||||
|
||||
function displayfiles() {
|
||||
const DisplayFiles = () => {
|
||||
const childrenList = []; // stores children details
|
||||
let loaded = false; // checks whether we have loaded the children details or not.
|
||||
let parStruct; // stores current struct(details, showChild)
|
||||
@ -18,8 +19,8 @@ function displayfiles() {
|
||||
? m(
|
||||
'td',
|
||||
m('i.fas.fa-angle-right', {
|
||||
class: 'fa-rotate-' + (parStruct.showChild ? '90' : '0'),
|
||||
style: 'margin-top:12px',
|
||||
class: `fa-rotate-${parStruct.showChild ? '90' : '0'}`,
|
||||
style: 'margin-top: 0.5rem',
|
||||
onclick: () => {
|
||||
if (!loaded) {
|
||||
// if it is not already retrieved
|
||||
@ -43,7 +44,7 @@ function displayfiles() {
|
||||
style: {
|
||||
position: 'relative',
|
||||
'--replyDepth': v.attrs.replyDepth,
|
||||
left: `calc(30px*${v.attrs.replyDepth})`,
|
||||
left: `calc(1.5rem*${v.attrs.replyDepth})`,
|
||||
},
|
||||
},
|
||||
parStruct.details.name
|
||||
@ -52,7 +53,7 @@ function displayfiles() {
|
||||
]),
|
||||
parStruct.showChild &&
|
||||
childrenList.map((child) =>
|
||||
m(displayfiles, {
|
||||
m(DisplayFiles, {
|
||||
// recursive call
|
||||
par_directory: { details: child, showChild: false },
|
||||
replyDepth: v.attrs.replyDepth + 1,
|
||||
@ -60,29 +61,46 @@ function displayfiles() {
|
||||
),
|
||||
],
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const Layout = () => {
|
||||
// let root_handle;
|
||||
let parent;
|
||||
let showShareManager = false;
|
||||
return {
|
||||
oninit: () => {
|
||||
rs.rsJsonApiRequest('/rsfiles/requestDirDetails', {}).then((res) => (parent = res));
|
||||
},
|
||||
view: () => [
|
||||
m('.widget__heading', [m('h3', 'My Files')]),
|
||||
m('.widget__heading', [
|
||||
m('h3', 'My Files'),
|
||||
m('button', { onclick: () => (showShareManager = true) }, 'Configure shared directories'),
|
||||
]),
|
||||
m('.widget__body', [
|
||||
m(
|
||||
util.MyFilesTable,
|
||||
m(
|
||||
'tbody',
|
||||
parent &&
|
||||
m(displayfiles, {
|
||||
m(DisplayFiles, {
|
||||
par_directory: { details: parent.body.details, showChild: false },
|
||||
replyDepth: 0,
|
||||
})
|
||||
)
|
||||
),
|
||||
m(
|
||||
'.shareManagerPopupOverlay#shareManagerPopup',
|
||||
{ style: { display: showShareManager ? 'block' : 'none' } },
|
||||
m(
|
||||
'.shareManagerPopup',
|
||||
m(manager),
|
||||
m(
|
||||
'button.red.close-btn',
|
||||
{ onclick: () => (showShareManager = false) },
|
||||
m('i.fas.fa-times')
|
||||
)
|
||||
)
|
||||
),
|
||||
]),
|
||||
],
|
||||
};
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// This file contains all application-wide Sass mixins.
|
||||
// -----------------------------------------------------------------------------
|
||||
@use './colors' as *;
|
||||
|
||||
/// Button Mixin
|
||||
@mixin button($bg-color) {
|
||||
@ -20,3 +21,44 @@
|
||||
box-shadow: inset 3px 3px 0 darken($color: $bg-color, $amount: 20);
|
||||
}
|
||||
}
|
||||
|
||||
/// Overlay Mixin
|
||||
@mixin popupOverlay {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
/// Blockquote Mixin
|
||||
@mixin blockquote($type) {
|
||||
position: relative;
|
||||
line-height: 1.2;
|
||||
@if ($type == 'info') {
|
||||
color: transparentize($dark-color, 0.2);
|
||||
border: 1px solid transparentize($primary-retro-color, 0.2);
|
||||
} @else if ($type == 'warning') {
|
||||
border: 1px solid transparentize($golden-yellow-color, 0.2);
|
||||
} @else if ($type == 'danger') {
|
||||
border: 1px solid transparentize($red-color, 0.2);
|
||||
}
|
||||
&::before {
|
||||
font-family: 'Font Awesome 5 Free';
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
left: 0.5rem;
|
||||
@if ($type == 'info') {
|
||||
content: '\f05a';
|
||||
color: $primary-color;
|
||||
} @else if ($type == 'warning') {
|
||||
content: '\f071';
|
||||
color: $golden-yellow-color;
|
||||
} @else if ($type == 'danger') {
|
||||
content: '\f05a';
|
||||
color: $red-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/******************************
|
||||
General site-wide rules
|
||||
******************************/
|
||||
@use '../abstracts/colors' as *;
|
||||
@use '../abstracts' as *;
|
||||
|
||||
#main {
|
||||
height: 100vh;
|
||||
@ -34,8 +34,8 @@ textarea {
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
padding: 0.4rem 0.8rem;
|
||||
border-radius: 0.25rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
/* Disable chromium's focused element hinting*/
|
||||
outline: transparent;
|
||||
}
|
||||
@ -112,8 +112,9 @@ hr {
|
||||
|
||||
.tooltip {
|
||||
color: #333;
|
||||
/*display: inline-block;*/
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
margin: 0 0.25rem;
|
||||
}
|
||||
.tooltiptext {
|
||||
visibility: hidden;
|
||||
@ -125,16 +126,25 @@ hr {
|
||||
z-index: 1;
|
||||
color: #ccc;
|
||||
background-color: #333;
|
||||
font-size: 0.9em;
|
||||
font-size: 0.875rem;
|
||||
text-align: center;
|
||||
padding: 5px 0;
|
||||
border-radius: 5px;
|
||||
padding: 0.25rem;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
.tooltip:hover .tooltiptext {
|
||||
visibility: visible;
|
||||
animation: fadein 0.5s;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
color: $dark-color;
|
||||
padding: 0.75rem 1rem 0.75rem 2rem;
|
||||
border-radius: 0.25rem;
|
||||
&.info {
|
||||
@include blockquote('info');
|
||||
}
|
||||
}
|
||||
|
||||
/******************************
|
||||
Animations
|
||||
******************************/
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
inset: 0;
|
||||
margin: auto;
|
||||
background-color: white;
|
||||
border-radius: 1rem;
|
||||
border-radius: 0.5rem;
|
||||
animation: fadein 0.5s;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@ -5,14 +5,13 @@
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
background-color: white;
|
||||
border-radius: 6px;
|
||||
border-radius: 0.5rem;
|
||||
overflow: auto;
|
||||
& .top-heading {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
&__heading {
|
||||
padding-bottom: 0.25rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
@ -42,9 +41,6 @@
|
||||
gap: 0.5rem;
|
||||
}
|
||||
}
|
||||
& .tooltip {
|
||||
float: right;
|
||||
}
|
||||
&-half {
|
||||
max-width: 50%;
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
@use '../abstracts/colors' as *;
|
||||
@use '../abstracts/' as *;
|
||||
|
||||
.file-view {
|
||||
width: 100%;
|
||||
@ -85,11 +85,15 @@ table.friendsfiles td:nth-child(2) {
|
||||
|
||||
&__keywords {
|
||||
flex-basis: 15%;
|
||||
padding-right: 0.25rem;
|
||||
border-right: 1px solid transparentize($dark-color, 0.9);
|
||||
|
||||
& .keywords-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-top: 2.5px solid transparentize($dark-color, 0.92);
|
||||
margin-top: 0.125rem;
|
||||
padding-top: 0.25rem;
|
||||
|
||||
& a {
|
||||
font-size: 1.2rem;
|
||||
@ -115,21 +119,20 @@ table.friendsfiles td:nth-child(2) {
|
||||
& th {
|
||||
font-size: 1.25rem;
|
||||
font-weight: bold;
|
||||
text-align: left;
|
||||
|
||||
&:nth-child(1) {
|
||||
flex-basis: 40%;
|
||||
text-align: left;
|
||||
}
|
||||
&:nth-child(2) {
|
||||
flex-basis: 10%;
|
||||
text-align: center;
|
||||
}
|
||||
&:nth-child(3) {
|
||||
flex-basis: 40%;
|
||||
text-align: left;
|
||||
}
|
||||
&:nth-child(4) {
|
||||
flex-basis: 10%;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -158,7 +161,7 @@ table.friendsfiles td:nth-child(2) {
|
||||
&__download {
|
||||
flex-basis: 10%;
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
justify-content: start;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
@ -178,3 +181,119 @@ table.friendsfiles td:nth-child(2) {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.shareManagerPopupOverlay {
|
||||
@include popupOverlay;
|
||||
.shareManagerPopup {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
margin: auto;
|
||||
width: 80%;
|
||||
height: 90%;
|
||||
& > .widget {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
& .close-btn {
|
||||
position: absolute;
|
||||
top: 1.5rem;
|
||||
right: 1.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.share-manager {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
&__table {
|
||||
margin: 1rem 0 auto;
|
||||
thead {
|
||||
font-weight: bold;
|
||||
text-align: left;
|
||||
td:nth-child(1),
|
||||
td:nth-child(2) {
|
||||
padding-left: 0.5rem;
|
||||
}
|
||||
td:nth-child(3),
|
||||
td:nth-child(4) {
|
||||
& .tooltip {
|
||||
font-weight: normal;
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
tbody {
|
||||
text-align: left;
|
||||
td:nth-child(4) {
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
td {
|
||||
input {
|
||||
border: 0 !important;
|
||||
&[type='text'] {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
&:nth-child(1) {
|
||||
width: 45%;
|
||||
}
|
||||
&:nth-child(2) {
|
||||
width: 20%;
|
||||
}
|
||||
&:nth-child(3) {
|
||||
width: 10%;
|
||||
}
|
||||
&:nth-child(4) {
|
||||
width: 25%;
|
||||
}
|
||||
}
|
||||
}
|
||||
&__actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
&__form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
&_input {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
input {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.share-flags {
|
||||
/* hide checkbox */
|
||||
input.share-flags-check {
|
||||
display: none;
|
||||
/* use label with 'for' to manipulate checkbox */
|
||||
& + label.share-flags-label {
|
||||
color: grey;
|
||||
margin-right: 0.25rem;
|
||||
padding: 0.25rem 0.25rem 0.125rem;
|
||||
border: 1px solid #6d6d6d;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
&:checked + label.share-flags-label {
|
||||
color: $primary-retro-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
label span {
|
||||
display: inline-block;
|
||||
width: 1.125rem;
|
||||
}
|
||||
}
|
||||
.manage-visibility {
|
||||
label {
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
}
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
@ -1,43 +1,51 @@
|
||||
/* Login */
|
||||
@use '../abstracts' as *;
|
||||
|
||||
.login-page {
|
||||
background-image: linear-gradient(-45deg, #0e76a7, #7bccff);
|
||||
background-image: linear-gradient(
|
||||
-45deg,
|
||||
transparentize($primary-color, 0.25),
|
||||
transparentize($primary-retro-color, 0.25)
|
||||
);
|
||||
height: 100%;
|
||||
animation: fadein 0.5s;
|
||||
}
|
||||
.login-container {
|
||||
background-color: white;
|
||||
box-shadow: 3px 3px 5px #444;
|
||||
margin: auto;
|
||||
position: relative;
|
||||
top: 100px;
|
||||
max-width: 400px;
|
||||
max-height: 500px;
|
||||
border-radius: 5px;
|
||||
.login-container {
|
||||
background-color: white;
|
||||
box-shadow: 3px 3px 5px transparentize($dark-color, 0.6);
|
||||
margin: auto;
|
||||
position: relative;
|
||||
top: 100px;
|
||||
max-width: 400px;
|
||||
max-height: 500px;
|
||||
border-radius: 5px;
|
||||
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.extra > label,
|
||||
.extra > br,
|
||||
.extra > input {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
& input {
|
||||
padding: 0.375rem 0.75rem;
|
||||
border-radius: 0.275rem;
|
||||
}
|
||||
|
||||
.login-container * {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.login-container > img {
|
||||
margin-top: 15px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
& * {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
& > img {
|
||||
margin: 1rem 0 2rem;
|
||||
}
|
||||
& extra {
|
||||
margin: 0;
|
||||
}
|
||||
& > a {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.login-container extra {
|
||||
margin: 0;
|
||||
}
|
||||
.login-container > a {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
.extra > label,
|
||||
.extra > br,
|
||||
.extra > input {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
@use '../abstracts/colors' as *;
|
||||
@use '../abstracts/' as *;
|
||||
|
||||
.side-bar {
|
||||
display: flex;
|
||||
@ -374,13 +374,7 @@ table.attachment-container {
|
||||
}
|
||||
|
||||
.composePopupOverlay {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
@include popupOverlay;
|
||||
.composePopup {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
|
||||
@ -29,7 +29,7 @@ shopt -s globstar
|
||||
|
||||
if [ "$2" = "" ]||[ "$2" = "index.html" ]; then
|
||||
echo copying html file
|
||||
cp -r $src/index.html $publicdest/
|
||||
cp $src/index.html $publicdest/
|
||||
fi
|
||||
|
||||
if [ "$2" = "" ]||[ "$2" = "styles.css" ]; then
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
"version": "1.0.0",
|
||||
"description": "Retroshare's Web Interface",
|
||||
"scripts": {
|
||||
"watch": "rm ./styles/app.css && sass --watch --embed-sources --embed-source-map ./app/scss/main.scss ./styles.css",
|
||||
"watch": "rm ./styles.css && sass --watch --embed-sources --embed-source-map ./app/scss/main.scss ./styles.css",
|
||||
"build": "sass --no-source-map --style=compressed ./app/scss/main.scss ./styles.css"
|
||||
},
|
||||
"license": "ISC",
|
||||
|
||||
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user