Implemented folder management.

This commit is contained in:
Tiberiu Chibici 2018-10-05 22:53:27 +03:00
parent 784d4deec8
commit c26732d101
19 changed files with 994 additions and 90 deletions

7
.idea/misc.xml Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptSettings">
<option name="languageLevel" value="ES6" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.6" project-jdk-type="Python SDK" />
</project>

8
.idea/modules.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/youtube-channel-manager.iml" filepath="$PROJECT_DIR$/.idea/youtube-channel-manager.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

51
.idea/watcherTasks.xml Normal file
View File

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectTasksOptions">
<TaskOptions isEnabled="true">
<option name="arguments" value="$FileName$ $FileNameWithoutExtension$.css --source-map" />
<option name="checkSyntaxErrors" value="true" />
<option name="description" />
<option name="exitCodeBehavior" value="ERROR" />
<option name="fileExtension" value="less" />
<option name="immediateSync" value="true" />
<option name="name" value="Less" />
<option name="output" value="$FileNameWithoutExtension$.css:$FileNameWithoutExtension$.css.map" />
<option name="outputFilters">
<array>
<FilterInfo>
<option name="description" value="" />
<option name="name" value="" />
<option name="regExp" value="$MESSAGE$\Q in \E$FILE_PATH$\Q on line \E$LINE$\Q, column \E$COLUMN$" />
</FilterInfo>
</array>
</option>
<option name="outputFromStdout" value="false" />
<option name="program" value="$USER_HOME$/AppData/Roaming/npm/lessc.cmd" />
<option name="runOnExternalChanges" value="true" />
<option name="scopeName" value="Project Files" />
<option name="trackOnlyRoot" value="true" />
<option name="workingDir" value="$FileDir$" />
<envs />
</TaskOptions>
<TaskOptions isEnabled="true">
<option name="arguments" value="--no-cache --update $FileName$:$FileNameWithoutExtension$.css" />
<option name="checkSyntaxErrors" value="true" />
<option name="description" />
<option name="exitCodeBehavior" value="ERROR" />
<option name="fileExtension" value="scss" />
<option name="immediateSync" value="true" />
<option name="name" value="SCSS" />
<option name="output" value="$FileNameWithoutExtension$.css:$FileNameWithoutExtension$.css.map" />
<option name="outputFilters">
<array />
</option>
<option name="outputFromStdout" value="false" />
<option name="program" value="scss" />
<option name="runOnExternalChanges" value="true" />
<option name="scopeName" value="Project Files" />
<option name="trackOnlyRoot" value="true" />
<option name="workingDir" value="$FileDir$" />
<envs />
</TaskOptions>
</component>
</project>

463
.idea/workspace.xml Normal file
View File

@ -0,0 +1,463 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ChangeListManager">
<list default="true" id="245751b6-c863-4572-8723-8499964fe105" name="Default Changelist" comment="">
<change afterPath="$PROJECT_DIR$/.idea/vcs.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/.idea/watcherTasks.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/YtManagerApp/management.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/YtManagerApp/migrations/0004_auto_20181005_1626.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/YtManagerApp/static/YtManagerApp/css/style.css.map" afterDir="false" />
<change afterPath="$PROJECT_DIR$/YtManagerApp/static/YtManagerApp/css/style.scss" afterDir="false" />
<change afterPath="$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/controls/subscription_edit_dialog.html" afterDir="false" />
<change afterPath="$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/js/subscription_tree.js" afterDir="false" />
<change beforePath="$PROJECT_DIR$/YtManager/urls.py" beforeDir="false" afterPath="$PROJECT_DIR$/YtManager/urls.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/YtManagerApp/static/YtManagerApp/css/style.css" beforeDir="false" afterPath="$PROJECT_DIR$/YtManagerApp/static/YtManagerApp/css/style.css" afterDir="false" />
<change beforePath="$PROJECT_DIR$/YtManagerApp/static/YtManagerApp/import/jstree/jstree.js" beforeDir="false" afterPath="$PROJECT_DIR$/YtManagerApp/static/YtManagerApp/import/jstree/jstree.js" afterDir="false" />
<change beforePath="$PROJECT_DIR$/YtManagerApp/static/YtManagerApp/js/subtree.js" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/controls/folder_edit_dialog.html" beforeDir="false" afterPath="$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/controls/folder_edit_dialog.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/index.html" beforeDir="false" afterPath="$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/index.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/YtManagerApp/views.py" beforeDir="false" afterPath="$PROJECT_DIR$/YtManagerApp/views.py" afterDir="false" />
</list>
<option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" />
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="DjangoConsoleOptions" custom-start-script="import sys; print('Python %s on %s' % (sys.version, sys.platform))&#10;import django; print('Django %s' % django.get_version())&#10;sys.path.extend([WORKING_DIR_AND_PYTHON_PATHS])&#10;if 'setup' in dir(django): django.setup()&#10;import django_manage_shell; django_manage_shell.run(PROJECT_ROOT)">
<option name="myCustomStartScript" value="import sys; print('Python %s on %s' % (sys.version, sys.platform))&#10;import django; print('Django %s' % django.get_version())&#10;sys.path.extend([WORKING_DIR_AND_PYTHON_PATHS])&#10;if 'setup' in dir(django): django.setup()&#10;import django_manage_shell; django_manage_shell.run(PROJECT_ROOT)" />
</component>
<component name="FUSProjectUsageTrigger">
<session id="1684258556">
<usages-collector id="statistics.lifecycle.project">
<counts>
<entry key="project.closed" value="1" />
<entry key="project.open.time.11" value="1" />
<entry key="project.open.time.3" value="1" />
<entry key="project.opened" value="2" />
</counts>
</usages-collector>
<usages-collector id="statistics.file.extensions.edit">
<counts>
<entry key="css" value="40" />
<entry key="html" value="620" />
<entry key="js" value="2858" />
<entry key="less" value="38" />
<entry key="py" value="1710" />
<entry key="py@youtube-channel-manager" value="19" />
<entry key="scss" value="26" />
</counts>
</usages-collector>
<usages-collector id="statistics.file.types.edit">
<counts>
<entry key="CSS" value="40" />
<entry key="CommandLine" value="19" />
<entry key="HTML" value="620" />
<entry key="JavaScript" value="2858" />
<entry key="Less" value="38" />
<entry key="Python" value="1710" />
<entry key="SCSS" value="26" />
</counts>
</usages-collector>
<usages-collector id="statistics.file.extensions.open">
<counts>
<entry key="css" value="1" />
<entry key="html" value="8" />
<entry key="js" value="5" />
<entry key="less" value="1" />
<entry key="py" value="7" />
<entry key="scss" value="3" />
</counts>
</usages-collector>
<usages-collector id="statistics.file.types.open">
<counts>
<entry key="CSS" value="1" />
<entry key="HTML" value="8" />
<entry key="JavaScript" value="5" />
<entry key="Less" value="1" />
<entry key="Python" value="7" />
<entry key="SCSS" value="3" />
</counts>
</usages-collector>
</session>
</component>
<component name="FileEditorManager">
<leaf SIDE_TABS_SIZE_LIMIT_KEY="300">
<file pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/YtManagerApp/views.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="334">
<caret line="62" lean-forward="true" selection-start-line="62" selection-end-line="62" />
<folding>
<element signature="e#0#35#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
</file>
<file pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/YtManagerApp/management.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="504">
<caret line="42" column="11" selection-start-line="42" selection-start-column="11" selection-end-line="42" selection-end-column="11" />
<folding>
<marker date="1538766618853" expanded="true" signature="90:91" ph="..." />
<marker date="1538766618853" expanded="true" signature="642:651" ph="..." />
</folding>
</state>
</provider>
</entry>
</file>
<file pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/js/subscription_tree.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="1700">
<caret line="100" column="186" selection-start-line="100" selection-start-column="186" selection-end-line="100" selection-end-column="186" />
</state>
</provider>
</entry>
</file>
<file pinned="false" current-in-tab="true">
<entry file="file://$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/controls/subscription_edit_dialog.html">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="357">
<caret line="21" column="30" lean-forward="true" selection-start-line="21" selection-start-column="30" selection-end-line="21" selection-end-column="30" />
<folding>
<element signature="e#433#440#1#HTML" expanded="true" />
</folding>
</state>
</provider>
</entry>
</file>
</leaf>
</component>
<component name="FileTemplateManagerImpl">
<option name="RECENT_TEMPLATES">
<list>
<option value="SCSS File" />
<option value="Python Script" />
</list>
</option>
</component>
<component name="FindInProjectRecents">
<findStrings>
<find>lds</find>
<find>get_selected</find>
<find>url</find>
<find>delete_fo</find>
<find>folder</find>
</findStrings>
<replaceStrings>
<replace>loading</replace>
<replace />
<replace>subscription</replace>
<replace>subscription</replace>
</replaceStrings>
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="IdeDocumentHistory">
<option name="CHANGED_PATHS">
<list>
<option value="$PROJECT_DIR$/YtManagerApp/static/YtManagerApp/css/style.css" />
<option value="$PROJECT_DIR$/YtManagerApp/static/YtManagerApp/css/style.less" />
<option value="$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/main_default.html" />
<option value="$PROJECT_DIR$/YtManagerApp/static/YtManagerApp/import/jstree/jstree.min.js" />
<option value="$PROJECT_DIR$/YtManagerApp/static/YtManagerApp/css/style.scss" />
<option value="$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/controls/folder_edit_dialog.html" />
<option value="$PROJECT_DIR$/YtManagerApp/views.py" />
<option value="$PROJECT_DIR$/YtManagerApp/management.py" />
<option value="$PROJECT_DIR$/YtManagerApp/static/YtManagerApp/js/subscription_tree.js" />
<option value="$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/index.html" />
<option value="$PROJECT_DIR$/YtManager/urls.py" />
<option value="$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/js/subscription_tree.js" />
<option value="$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/controls/subscription_edit_dialog.html" />
</list>
</option>
</component>
<component name="JsBuildToolGruntFileManager" detection-done="true" sorting="DEFINITION_ORDER" />
<component name="JsBuildToolPackageJson" detection-done="true" sorting="DEFINITION_ORDER" />
<component name="JsGulpfileManager">
<detection-done>true</detection-done>
<sorting>DEFINITION_ORDER</sorting>
</component>
<component name="ProjectFrameBounds" extendedState="6">
<option name="x" value="-8" />
<option name="y" value="1" />
<option name="width" value="1400" />
<option name="height" value="1000" />
</component>
<component name="ProjectView">
<navigator proportions="" version="1">
<foldersAlwaysOnTop value="true" />
</navigator>
<panes>
<pane id="ProjectPane">
<subPane>
<expand>
<path>
<item name="youtube-channel-manager" type="b2602c69:ProjectViewProjectNode" />
<item name="youtube-channel-manager" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="youtube-channel-manager" type="b2602c69:ProjectViewProjectNode" />
<item name="youtube-channel-manager" type="462c0819:PsiDirectoryNode" />
<item name="YtManager" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="youtube-channel-manager" type="b2602c69:ProjectViewProjectNode" />
<item name="youtube-channel-manager" type="462c0819:PsiDirectoryNode" />
<item name="YtManagerApp" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="youtube-channel-manager" type="b2602c69:ProjectViewProjectNode" />
<item name="youtube-channel-manager" type="462c0819:PsiDirectoryNode" />
<item name="YtManagerApp" type="462c0819:PsiDirectoryNode" />
<item name="static" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="youtube-channel-manager" type="b2602c69:ProjectViewProjectNode" />
<item name="youtube-channel-manager" type="462c0819:PsiDirectoryNode" />
<item name="YtManagerApp" type="462c0819:PsiDirectoryNode" />
<item name="static" type="462c0819:PsiDirectoryNode" />
<item name="YtManagerApp" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="youtube-channel-manager" type="b2602c69:ProjectViewProjectNode" />
<item name="youtube-channel-manager" type="462c0819:PsiDirectoryNode" />
<item name="YtManagerApp" type="462c0819:PsiDirectoryNode" />
<item name="templates" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="youtube-channel-manager" type="b2602c69:ProjectViewProjectNode" />
<item name="youtube-channel-manager" type="462c0819:PsiDirectoryNode" />
<item name="YtManagerApp" type="462c0819:PsiDirectoryNode" />
<item name="templates" type="462c0819:PsiDirectoryNode" />
<item name="YtManagerApp" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="youtube-channel-manager" type="b2602c69:ProjectViewProjectNode" />
<item name="youtube-channel-manager" type="462c0819:PsiDirectoryNode" />
<item name="YtManagerApp" type="462c0819:PsiDirectoryNode" />
<item name="templates" type="462c0819:PsiDirectoryNode" />
<item name="YtManagerApp" type="462c0819:PsiDirectoryNode" />
<item name="controls" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="youtube-channel-manager" type="b2602c69:ProjectViewProjectNode" />
<item name="youtube-channel-manager" type="462c0819:PsiDirectoryNode" />
<item name="YtManagerApp" type="462c0819:PsiDirectoryNode" />
<item name="templates" type="462c0819:PsiDirectoryNode" />
<item name="YtManagerApp" type="462c0819:PsiDirectoryNode" />
<item name="js" type="462c0819:PsiDirectoryNode" />
</path>
</expand>
<select />
</subPane>
</pane>
<pane id="Scope" />
</panes>
</component>
<component name="PropertiesComponent">
<property name="SearchEverywhereHistoryKey" value="&#9;FILE&#9;file://D:/Dev/youtube-channel-manager/YtManagerApp/views.py" />
<property name="WebServerToolWindowFactoryState" value="false" />
<property name="last_opened_file_path" value="$USER_HOME$/AppData/Roaming/npm/lessc.cmd" />
<property name="list.type.of.created.stylesheet" value="SCSS" />
<property name="nodejs_interpreter_path.stuck_in_default_project" value="undefined stuck path" />
<property name="nodejs_npm_path_reset_for_default_project" value="true" />
<property name="settings.editor.selected.configurable" value="preferences.sourceCode" />
</component>
<component name="RecentsManager">
<key name="CopyFile.RECENT_KEYS">
<recent name="D:\Dev\youtube-channel-manager\YtManagerApp\templates\YtManagerApp\controls" />
</key>
<key name="MoveFile.RECENT_KEYS">
<recent name="D:\Dev\youtube-channel-manager\YtManagerApp\templates\YtManagerApp" />
</key>
</component>
<component name="RunDashboard">
<option name="ruleStates">
<list>
<RuleState>
<option name="name" value="ConfigurationTypeDashboardGroupingRule" />
</RuleState>
<RuleState>
<option name="name" value="StatusDashboardGroupingRule" />
</RuleState>
</list>
</option>
</component>
<component name="RunManager">
<configuration name="youtube-channel-manager" type="Python.DjangoServer" factoryName="Django server">
<module name="youtube-channel-manager" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
<env name="DJANGO_SETTINGS_MODULE" value="YtManager.settings" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="" />
<option name="IS_MODULE_SDK" value="false" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<option name="launchJavascriptDebuger" value="false" />
<option name="port" value="8000" />
<option name="host" value="" />
<option name="additionalOptions" value="" />
<option name="browserUrl" value="" />
<option name="runTestServer" value="false" />
<option name="runNoReload" value="false" />
<option name="useCustomRunCommand" value="false" />
<option name="customRunCommand" value="" />
<method v="2" />
</configuration>
</component>
<component name="SvnConfiguration">
<configuration />
</component>
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="245751b6-c863-4572-8723-8499964fe105" name="Default Changelist" comment="" />
<created>1538745535248</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1538745535248</updated>
</task>
<servers />
</component>
<component name="ToolWindowManager">
<frame x="-8" y="-8" width="1936" height="1056" extended-state="6" />
<editor active="true" />
<layout>
<window_info active="true" content_ui="combo" id="Project" order="0" visible="true" weight="0.18017058" />
<window_info id="Structure" order="1" side_tool="true" weight="0.25" />
<window_info id="Favorites" order="2" side_tool="true" />
<window_info anchor="bottom" id="Message" order="0" />
<window_info anchor="bottom" id="Find" order="1" sideWeight="0.49946696" weight="0.329718" />
<window_info anchor="bottom" id="Run" order="2" sideWeight="0.4978678" visible="true" weight="0.30911064" />
<window_info anchor="bottom" id="Debug" order="3" sideWeight="0.49946696" weight="0.3991323" />
<window_info anchor="bottom" id="Cvs" order="4" weight="0.25" />
<window_info anchor="bottom" id="Inspection" order="5" weight="0.4" />
<window_info anchor="bottom" id="TODO" order="6" />
<window_info anchor="bottom" id="manage.py@youtube-channel-manager" order="7" weight="0.3290993" />
<window_info anchor="bottom" id="Docker" order="8" show_stripe_button="false" />
<window_info anchor="bottom" id="Database Changes" order="9" show_stripe_button="false" />
<window_info anchor="bottom" id="Event Log" order="10" sideWeight="0.5021322" side_tool="true" visible="true" weight="0.30911064" />
<window_info anchor="bottom" id="Version Control" order="11" />
<window_info anchor="bottom" id="Terminal" order="12" />
<window_info anchor="bottom" id="Python Console" order="13" weight="0.3290993" />
<window_info anchor="right" id="Commander" internal_type="SLIDING" order="0" type="SLIDING" weight="0.4" />
<window_info anchor="right" id="Ant Build" order="1" weight="0.25" />
<window_info anchor="right" content_ui="combo" id="Hierarchy" order="2" weight="0.25" />
<window_info anchor="right" id="SciView" order="3" />
<window_info anchor="right" id="Database" order="4" />
</layout>
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="1" />
</component>
<component name="VcsContentAnnotationSettings">
<option name="myLimit" value="2678400000" />
</component>
<component name="editorHistoryManager">
<entry file="file://$PROJECT_DIR$/YtManagerApp/static/YtManagerApp/css/style.less">
<provider selected="true" editor-type-id="text-editor">
<state>
<caret column="18" lean-forward="true" selection-start-column="18" selection-end-column="22" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/YtManagerApp/static/YtManagerApp/import/jstree/jstree.min.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="85">
<caret line="5" column="3166" selection-start-line="5" selection-start-column="3166" selection-end-line="5" selection-end-column="3166" />
<folding>
<element signature="n#style#0;n#ins#0;n#div#0;n#!!top" expanded="true" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/YtManagerApp/static/YtManagerApp/css/_style.css">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="272">
<caret line="16" lean-forward="true" selection-start-line="16" selection-end-line="17" selection-end-column="26" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/YtManagerApp/static/YtManagerApp/css/style.scss">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="170">
<caret line="10" lean-forward="true" selection-start-line="10" selection-end-line="10" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/controls/folder_edit_dialog.html">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="221">
<caret line="13" column="72" selection-start-line="13" selection-start-column="72" selection-end-line="13" selection-end-column="72" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/main_default.html">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="357">
<caret line="26" column="4" lean-forward="true" selection-start-line="26" selection-start-column="4" selection-end-line="26" selection-end-column="4" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/YtManagerApp/views.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="334">
<caret line="62" lean-forward="true" selection-start-line="62" selection-end-line="62" />
<folding>
<element signature="e#0#35#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/YtManagerApp/management.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="504">
<caret line="42" column="11" selection-start-line="42" selection-start-column="11" selection-end-line="42" selection-end-column="11" />
<folding>
<marker date="1538766618853" expanded="true" signature="90:91" ph="..." />
<marker date="1538766618853" expanded="true" signature="642:651" ph="..." />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/YtManager/urls.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="272">
<caret line="19" lean-forward="true" selection-start-line="19" selection-end-line="19" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/index.html">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="113">
<caret line="20" column="28" selection-start-line="20" selection-start-column="28" selection-end-line="20" selection-end-column="28" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/js/subscription_tree.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="1700">
<caret line="100" column="186" selection-start-line="100" selection-start-column="186" selection-end-line="100" selection-end-column="186" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/controls/subscription_edit_dialog.html">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="357">
<caret line="21" column="30" lean-forward="true" selection-start-line="21" selection-start-column="30" selection-end-line="21" selection-end-column="30" />
<folding>
<element signature="e#433#440#1#HTML" expanded="true" />
</folding>
</state>
</provider>
</entry>
</component>
</project>

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="FacetManager">
<facet type="django" name="Django">
<configuration>
<option name="rootFolder" value="$MODULE_DIR$" />
<option name="settingsModule" value="YtManager/settings.py" />
<option name="manageScript" value="$MODULE_DIR$/manage.py" />
<option name="environment" value="&lt;map/&gt;" />
<option name="doNotUseTestRunner" value="false" />
<option name="trackFilePattern" value="migrations" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="TemplatesService">
<option name="TEMPLATE_CONFIGURATION" value="Django" />
<option name="TEMPLATE_FOLDERS">
<list>
<option value="$MODULE_DIR$/YtManagerApp/templates" />
</list>
</option>
</component>
<component name="TestRunnerService">
<option name="PROJECT_TEST_RUNNER" value="Unittests" />
</component>
</module>

View File

@ -22,5 +22,7 @@ urlpatterns = [
path('admin/', admin.site.urls), path('admin/', admin.site.urls),
path('ajax/get_children', views.ajax_get_children, name='ajax_get_children'), path('ajax/get_children', views.ajax_get_children, name='ajax_get_children'),
path('ajax/get_folders', views.ajax_get_folders, name='ajax_get_folders'), path('ajax/get_folders', views.ajax_get_folders, name='ajax_get_folders'),
path('ajax/edit_folder', views.ajax_edit_folder, name='ajax_edit_folder'),
path('ajax/delete_folder/<int:fid>/', views.ajax_delete_folder, name='ajax_delete_folder'),
path(r'', views.index, name='home') path(r'', views.index, name='home')
] ]

View File

@ -0,0 +1,44 @@
from .models import SubscriptionFolder, Subscription, Video
class FolderManager(object):
@staticmethod
def create_or_edit(fid, name, parent_id):
# Create or edit
if fid == '#':
folder = SubscriptionFolder()
else:
folder = SubscriptionFolder.objects.get(id=int(fid))
# Set attributes
folder.name = name
if parent_id == '#':
folder.parent = None
else:
folder.parent = SubscriptionFolder.objects.get(id=int(parent_id))
FolderManager.__validate(folder)
folder.save()
@staticmethod
def __validate(folder):
# Make sure folder name is unique in the parent folder
for dbFolder in SubscriptionFolder.objects.filter(parent_id=folder.parent_id):
if dbFolder.id != folder.id and dbFolder.name == folder.name:
raise ValueError('Folder name is not unique!')
# Prevent parenting loops
current = folder
visited = []
while not (current is None):
if current in visited:
raise ValueError('Parenting cycle detected!')
visited.append(current)
current = current.parent
@staticmethod
def delete(fid: int):
folder = SubscriptionFolder.objects.get(id=fid)
folder.delete()

View File

@ -0,0 +1,18 @@
# Generated by Django 2.1.2 on 2018-10-05 13:26
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('YtManagerApp', '0003_auto_20181003_1825'),
]
operations = [
migrations.AlterField(
model_name='video',
name='downloaded_path',
field=models.TextField(blank=True, null=True),
),
]

View File

@ -1,9 +1,31 @@
/* Some material font helpers */ /* Some material font helpers */
.material-folder::before { .material-folder::before {
content: "\e2c7"; content: "\e2c7"; }
}
.material-person::before { .material-person::before {
content: "\e7fd"; content: "\e7fd"; }
}
/* Loading animation */
.loading-dual-ring {
display: inline-block;
width: 64px;
height: 64px; }
.loading-dual-ring:after {
content: " ";
display: block;
width: 46px;
height: 46px;
margin: 1px;
border-radius: 50%;
border: 5px solid #007bff;
border-color: #007bff transparent #007bff transparent;
animation: loading-dual-ring 1.2s linear infinite; }
@keyframes loading-dual-ring {
0% {
transform: rotate(0deg); }
100% {
transform: rotate(360deg); } }
/*# sourceMappingURL=style.css.map */

View File

@ -0,0 +1,7 @@
{
"version": 3,
"mappings": "AAEA,gCAAgC;AAChC,wBAAyB;EACrB,OAAO,EAAE,OAAO;;AAGpB,wBAAyB;EACrB,OAAO,EAAE,OAAO;;AAGpB,uBAAuB;AACvB,kBAAmB;EACf,OAAO,EAAE,YAAY;EACrB,KAAK,EAAE,IAAI;EACX,MAAM,EAAE,IAAI;;AAGhB,wBAAyB;EACrB,OAAO,EAAE,GAAG;EACZ,OAAO,EAAE,KAAK;EACd,KAAK,EAAE,IAAI;EACX,MAAM,EAAE,IAAI;EACZ,MAAM,EAAE,GAAG;EACX,aAAa,EAAE,GAAG;EAClB,MAAM,EAAE,iBAAuB;EAC/B,YAAY,EAAE,uCAAmD;EACjE,SAAS,EAAE,sCAAsC;;AAGrD,4BAOC;EANG,EAAG;IACC,SAAS,EAAE,YAAY;EAE3B,IAAK;IACD,SAAS,EAAE,cAAc",
"sources": ["style.scss"],
"names": [],
"file": "style.css"
}

View File

@ -0,0 +1,38 @@
$accent-color: #007bff;
/* Some material font helpers */
.material-folder::before {
content: "\e2c7";
}
.material-person::before {
content: "\e7fd";
}
/* Loading animation */
.loading-dual-ring {
display: inline-block;
width: 64px;
height: 64px;
}
.loading-dual-ring:after {
content: " ";
display: block;
width: 46px;
height: 46px;
margin: 1px;
border-radius: 50%;
border: 5px solid $accent-color;
border-color: $accent-color transparent $accent-color transparent;
animation: loading-dual-ring 1.2s linear infinite;
}
@keyframes loading-dual-ring {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}

View File

@ -4592,7 +4592,7 @@
if(theme_url === true) { if(theme_url === true) {
var dir = this.settings.core.themes.dir; var dir = this.settings.core.themes.dir;
if(!dir) { dir = $.jstree.path + '/themes'; } if(!dir) { dir = $.jstree.path + '/themes'; }
theme_url = dir + '/' + theme_name + '/style.css'; theme_url = dir + '/' + theme_name + '/_style.css';
} }
if(theme_url && $.inArray(theme_url, themes_loaded) === -1) { if(theme_url && $.inArray(theme_url, themes_loaded) === -1) {
$('head').append('<'+'link rel="stylesheet" href="' + theme_url + '" type="text/css" />'); $('head').append('<'+'link rel="stylesheet" href="' + theme_url + '" type="text/css" />');

View File

@ -1,50 +0,0 @@
function onSelectionChanged(e, data)
{
node = data.instance.get_selected(true)[0];
}
function validateChange(operation, node, parent, position, more)
{
if (more.dnd)
{
// create_node, rename_node, delete_node, move_node and copy_node
if (operation === "copy_node" || operation === "move_node")
{
if (more.ref.type === "sub")
return false;
}
}
return true;
}
function setupTree(dataIn)
{
$("#tree-wrapper").jstree({
core : {
data : {
url : 'ajax/get_children'
},
check_callback : validateChange,
themes : {
dots : false
},
},
types : {
folder : {
icon : "material-icons material-folder"
},
sub : {
icon : "material-icons material-person",
max_depth : 0
}
},
plugins : [ "types", "wholerow", "dnd" ]
});
$("#tree-wrapper").on("changed.jstree", onSelectionChanged);
}
$(document).ready(function ()
{
setupTree();
})

View File

@ -2,30 +2,38 @@
<div class="modal-dialog" role="document"> <div class="modal-dialog" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title">Edit folder</h5> <h5 id="folder_edit_dialog_title" class="modal-title">Edit folder</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> <button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
</div> </div>
<div class="modal-body"> <div id="folder_edit_dialog_loading" class="modal-body">
<form> <div class="loading-dual-ring"></div>
<div id="folder_edit_dialog_error"></div>
</div>
<form id="folder_edit_dialog_form" action="{% url 'ajax_edit_folder' %}" method="post">
<div class="modal-body">
{% csrf_token %}
<input type="hidden" id="folder_edit_dialog_id" name="id" value="#">
<div class="form-group row"> <div class="form-group row">
<label class="col-sm-4" for="folder_edit_dialog_name">Name</label> <label class="col-sm-3" for="folder_edit_dialog_name">Name</label>
<div class="col-sm-8"> <div class="col-sm-9">
<input type="text" class="form-control" id="folder_edit_dialog_name" placeholder="Folder name"> <input type="text" class="form-control" id="folder_edit_dialog_name" name="name" placeholder="Folder name">
</div> </div>
</div> </div>
<div class="form-check row"> <div class="form-group row">
<input type="checkbox" class="form-check-input" id="exampleCheck1"> <label class="col-sm-3" for="folder_edit_dialog_parent">Parent folder</label>
<label class="form-check-label" for="exampleCheck1">Check me out</label> <div class="col-sm-9">
<select class="form-control" id="folder_edit_dialog_parent" name="parent">
</select>
</div>
</div> </div>
<button type="submit" class="btn btn-primary">Submit</button> </div>
</form> <div class="modal-footer">
</div> <button id="folder_edit_dialog_submit" type="submit" class="btn btn-primary">Submit</button>
<div class="modal-footer"> <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary">Save changes</button> </div>
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button> </form>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -0,0 +1,39 @@
<div id="subscription_edit_dialog" class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 id="subscription_edit_dialog_title" class="modal-title">Edit subscription</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div id="subscription_edit_dialog_loading" class="modal-body">
<div class="loading-dual-ring"></div>
<div id="subscription_edit_dialog_error"></div>
</div>
<form id="subscription_edit_dialog_form" action="{% url 'ajax_edit_subscription' %}" method="post">
<div class="modal-body">
{% csrf_token %}
<input type="hidden" id="subscription_edit_dialog_id" name="id" value="#">
<div class="form-group row">
<label class="col-sm-3" for="subscription_edit_dialog_url">Link:</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="subscription_edit_dialog_name" name="name" placeholder="subscription name">
</div>
</div>
<div class="form-group row">
<label class="col-sm-3" for="subscription_edit_dialog_parent">Parent subscription</label>
<div class="col-sm-9">
<select class="form-control" id="subscription_edit_dialog_parent" name="parent">
</select>
</div>
</div>
</div>
<div class="modal-footer">
<button id="subscription_edit_dialog_submit" type="submit" class="btn btn-primary">Submit</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</form>
</div>
</div>
</div>

View File

@ -2,25 +2,38 @@
{% load static %} {% load static %}
{% block stylesheets %} {% block stylesheets %}
<link rel="stylesheet" href="{% static 'YtManagerApp/import/jstree/themes/default/style.min.css' %}" /> <link rel="stylesheet" href="{% static 'YtManagerApp/import/jstree/themes/default/style.min.css' %}" />
{% endblock %} {% endblock %}
{% block scripts %} {% block scripts %}
<script src="{% static 'YtManagerApp/import/jstree/jstree.js' %}"></script> <script src="{% static 'YtManagerApp/import/jstree/jstree.js' %}"></script>
<script src="{% static 'YtManagerApp/js/subtree.js' %}"></script> <script>
{% include 'YtManagerApp/js/subscription_tree.js' %}
</script>
{% include 'YtManagerApp/controls/folder_edit_dialog.html' %}
{% endblock %} {% endblock %}
{% block master %} {% block master %}
<div class="btn-toolbar" role="toolbar" aria-label="Subscriptions toolbar"> <div class="btn-toolbar" role="toolbar" aria-label="Subscriptions toolbar">
<div class="btn-group btn-group-sm mr-2" role="group"> <div class="btn-group btn-group-sm mr-2" role="group">
<button type="button" class="btn btn-secondary"> <button type="button" class="btn btn-secondary" >
<i class="material-icons" aria-hidden="true">add</i> <i class="material-icons" aria-hidden="true">add</i>
</button> </button>
<button type="button" class="btn btn-secondary"> <button id="btn_create_folder" type="button" class="btn btn-secondary">
<i class="material-icons" aria-hidden="true">create_new_folder</i> <i class="material-icons" aria-hidden="true">create_new_folder</i>
</button> </button>
</div> </div>
<div class="btn-group btn-group-sm mr-2" role="group">
<button id="btn_edit_node" type="button" class="btn btn-secondary" >
<i class="material-icons" aria-hidden="true">edit</i>
</button>
<button id="btn_delete_node" type="button" class="btn btn-secondary" >
<i class="material-icons" aria-hidden="true">delete</i>
</button>
</div>
</div> </div>
<div id="tree-wrapper"> <div id="tree-wrapper">

View File

@ -0,0 +1,174 @@
function folderEditDialog_Show(isNew, editNode)
{
let dialog = $("#folder_edit_dialog");
dialog.find('#folder_edit_dialog_title').text(isNew ? "New folder" : "Edit folder");
dialog.find("#folder_edit_dialog_loading").show();
dialog.find("#folder_edit_dialog_error").hide();
dialog.find("#folder_edit_dialog_form").hide();
dialog.modal();
$.get("{% url 'ajax_get_folders' %}")
.done(function(folders)
{
// Populate list of folders
let selParent = dialog.find("#folder_edit_dialog_parent");
selParent.empty();
selParent.append(new Option('(None)', '#'));
let parentId = null;
if (!isNew) {
parentId = editNode.parent.replace('folder', '');
}
for (let folder of folders)
{
let o = new Option(folder.text, folder.id);
if (!isNew && folder.id.toString() === parentId.toString())
o.selected = true;
selParent.append(o);
}
// Show form
dialog.find("#folder_edit_dialog_loading").hide();
dialog.find("#folder_edit_dialog_form").show();
dialog.find("#folder_edit_dialog_submit").text(isNew ? "Create" : "Save");
if (isNew)
{
dialog.find("#folder_edit_dialog_id").val('#');
dialog.find("#folder_edit_dialog_name").val('');
}
if (!isNew)
{
idTrimmed = editNode.id.replace('folder', '');
dialog.find("#folder_edit_dialog_id").val(idTrimmed);
dialog.find("#folder_edit_dialog_name").val(editNode.text);
}
})
.fail(function() {
let msgError = dialog.find("#folder_edit_dialog_error");
msgError.show();
msgError.text("An error occurred!");
});
}
function folderEditDialog_ShowNew()
{
folderEditDialog_Show(true, null);
}
function folderEditDialog_Close()
{
$("#folder_edit_dialog").modal('hide');
}
function folderEditDialog_Submit(e)
{
let form = $(this);
let url = form.attr('action');
$.post(url, form.serialize())
.done(tree_Refresh);
folderEditDialog_Close();
e.preventDefault();
}
function treeNode_Edit()
{
let selectedNodes = $("#tree-wrapper").jstree('get_selected', true);
if (selectedNodes.length === 1)
{
let node = selectedNodes[0];
if (node.type === 'folder') {
folderEditDialog_Show(false, node);
}
else {
// TODO...
}
}
}
function treeNode_Delete()
{
let selectedNodes = $("#tree-wrapper").jstree('get_selected', true);
if (selectedNodes.length === 1)
{
let node = selectedNodes[0];
if (node.type === 'folder') {
let folderId = node.id.toString().replace('folder', '');
if (confirm('Are you sure you want to delete folder "' + node.text + '" and all its descendants?\nNote: the subscriptions won\'t be deleted, they will only be moved outside.'))
{
$.post("{% url 'ajax_delete_folder' 99999 %}".replace('99999', folderId), {
csrfmiddlewaretoken: '{{ csrf_token }}'
}).done(tree_Refresh);
}
}
else {
// TODO...
}
}
}
function tree_Initialize()
{
let treeWrapper = $("#tree-wrapper");
treeWrapper.jstree({
core : {
data : {
url : "{% url 'ajax_get_children' %}"
},
check_callback : tree_ValidateChange,
themes : {
dots : false
},
},
types : {
folder : {
icon : "material-icons material-folder"
},
sub : {
icon : "material-icons material-person",
max_depth : 0
}
},
plugins : [ "types", "wholerow", "dnd" ]
});
treeWrapper.on("changed.jstree", tree_OnSelectionChanged);
}
function tree_Refresh()
{
$("#tree-wrapper").jstree("refresh");
}
function tree_ValidateChange(operation, node, parent, position, more)
{
if (more.dnd)
{
// create_node, rename_node, delete_node, move_node and copy_node
if (operation === "copy_node" || operation === "move_node")
{
if (more.ref.type === "sub")
return false;
}
}
return true;
}
function tree_OnSelectionChanged(e, data)
{
node = data.instance.get_selected(true)[0];
}
$(document).ready(function ()
{
tree_Initialize();
$("#btn_create_folder").on("click", folderEditDialog_ShowNew);
$("#btn_edit_node").on("click", treeNode_Edit);
$("#btn_delete_node").on("click", treeNode_Delete);
$("#folder_edit_dialog_form").submit(folderEditDialog_Submit);
});

View File

@ -1,36 +1,42 @@
from django.shortcuts import render from django.shortcuts import render
from django.http import HttpResponse, HttpRequest, JsonResponse from django.http import HttpResponse, HttpRequest, JsonResponse
from .models import SubscriptionFolder, Subscription from .models import SubscriptionFolder, Subscription
from .management import FolderManager
def get_children_recurse(parent_id): def get_children_recurse(parent_id):
children = [] children = []
for folder in SubscriptionFolder.objects.filter(parent_id=parent_id).order_by('name'): for folder in SubscriptionFolder.objects.filter(parent_id=parent_id).order_by('name'):
children.append({ children.append({
"id" : "folder" + str(folder.id), "id": "folder" + str(folder.id),
"text" : folder.name, "text": folder.name,
"type" : "folder", "type": "folder",
"children" : get_children_recurse(folder.id) "state": {"opened": True},
"children": get_children_recurse(folder.id)
}) })
for sub in Subscription.objects.filter(parent_folder_id=parent_id).order_by('name'): for sub in Subscription.objects.filter(parent_folder_id=parent_id).order_by('name'):
children.append({ children.append({
"id" : "sub" + str(sub.id), "id": "sub" + str(sub.id),
"type" : "sub", "type": "sub",
"text" : sub.name "text": sub.name
}) })
return children return children
def get_folders(parent_id, path = ""): def get_folders(parent_id, path=""):
folders = [] folders = []
prefix = path + "/"
if len(path) == 0:
prefix = ""
for folder in SubscriptionFolder.objects.filter(parent_id=parent_id).order_by('name'): for folder in SubscriptionFolder.objects.filter(parent_id=parent_id).order_by('name'):
folder_path = path + "/" + folder.name folder_path = prefix + folder.name
folders.append({ folders.append({
"id" : "folder" + str(folder.id), "id": folder.id,
"text" : folder_path "text": folder_path
}) })
folders.extend(get_folders(folder.id, folder_path)) folders.extend(get_folders(folder.id, folder_path))
@ -40,9 +46,26 @@ def get_folders(parent_id, path = ""):
def ajax_get_children(request: HttpRequest): def ajax_get_children(request: HttpRequest):
return JsonResponse(get_children_recurse(None), safe=False) return JsonResponse(get_children_recurse(None), safe=False)
def ajax_get_folders(request: HttpRequest): def ajax_get_folders(request: HttpRequest):
return JsonResponse(get_folders(None), safe=False) return JsonResponse(get_folders(None), safe=False)
def ajax_edit_folder(request: HttpRequest):
if request.method == 'POST':
fid = request.POST['id']
name = request.POST['name']
parent_id = request.POST['parent']
FolderManager.create_or_edit(fid, name, parent_id)
return HttpResponse()
def ajax_delete_folder(request: HttpRequest, fid):
FolderManager.delete(fid)
return HttpResponse()
def index(request: HttpRequest): def index(request: HttpRequest):
context = {} context = {}
return render(request, 'YtManagerApp/index.html', context) return render(request, 'YtManagerApp/index.html', context)