Merge pull request #84 from zelfroster/share-manager

Share manager for managing shared directories with different permissions
This commit is contained in:
csoler 2023-09-09 20:31:45 +02:00 committed by GitHub
commit 542a8c07bd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 649 additions and 146 deletions

View File

@ -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', [

View 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;

View File

@ -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),
};

View File

@ -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,
};

View File

@ -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')
)
)
),
]),
],
};

View File

@ -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;
}
}
}

View File

@ -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
******************************/

View File

@ -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;

View File

@ -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%;
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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

View File

@ -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